Constructor de movimiento
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 21 de 104.
Un constructor de movimiento transfiere recursos desde un objeto temporal en lugar de copiarlos. Mientras que un constructor de copia duplica los datos, un constructor de movimiento «roba» los recursos, dejando el objeto de origen en un estado válido pero vacío. Esto evita copias profundas costosas cuando el objeto de origen está a punto de destruirse de todos modos.
El constructor de movimiento recibe una referencia a un rvalue, denotada por &&:
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// Constructor de movimiento
Buffer(Buffer&& other) noexcept {
data = other.data; // Robar el puntero
size = other.size;
other.data = nullptr; // Dejar el origen en un estado válido
other.size = 0;
}
~Buffer() { delete[] data; }
};La diferencia clave con respecto a copiar: no asignamos memoria nueva. Simplemente asumimos la propiedad de la memoria existente y establecemos el puntero de origen en nullptr para que su destructor no elimine nuestros datos.
Los constructores de movimiento se llaman al inicializar desde objetos temporales o al usar std::move():
Buffer createBuffer() {
return Buffer(1000); // Se utiliza el constructor de movimiento (valor de retorno)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Movimiento explícito - b1 ahora está vacíoMarca los constructores de movimiento como noexcept cuando sea posible. Esto le indica al compilador que la operación no lanzará excepciones, lo que permite optimizaciones importantes en contenedores como std::vector.
Desafío
FácilConstruyamos un sistema de paquetes de datos que demuestre cómo los constructores de movimiento transfieren eficientemente la propiedad de los recursos. Crearás una clase DataPacket que administra un arreglo de bytes asignado dinámicamente e implementarás un constructor de movimiento que "toma" los datos en lugar de copiarlos.
Crearás dos archivos para organizar tu código:
DataPacket.h: Define una claseDataPacketque administre una carga útil de datos enteros. Tu clase debe tener:- Miembros privados: un puntero a un arreglo de enteros (
payload), unsizepara el número de elementos y unpacketId(cadena) para identificar el paquete - Un constructor parametrizado que reciba un ID de paquete y un tamaño, asigne el arreglo y lo llene con valores de 0 a tamaño-1. Imprime
"Packet <id> created with size <size>" - Un constructor de movimiento marcado como
noexceptque transfiera la propiedad de la carga útil desde el objeto de origen. Imprime"Packet <id> moved". Recuerda dejar el objeto de origen en un estado vacío válido (nullptr, tamaño 0) - Un destructor que libere la memoria si el puntero no es nulo e imprima
"Packet <id> destroyed" - Un método
getSize()que devuelva el tamaño actual - Un método
getId()que devuelva el ID del paquete - Un método
getSum()que devuelva la suma de todos los elementos de la carga útil (devuelve 0 si payload es nulo)
- Miembros privados: un puntero a un arreglo de enteros (
main.cpp: Demuestra la construcción por movimiento leyendo un ID de paquete y un tamaño desde la entrada. Después:- Crea un
DataPacketllamadooriginalcon los valores de entrada - Imprime
"Original - ID: <id>, Size: <size>, Sum: <sum>" - Crea un paquete nuevo llamado
transferredmoviendo desdeoriginalmediantestd::move() - Imprime
"After move:" - Imprime
"Original - ID: <id>, Size: <size>, Sum: <sum>" - Imprime
"Transferred - ID: <id>, Size: <size>, Sum: <sum>"
- Crea un
El formato de entrada será:
- Primera línea: ID del paquete (cadena)
- Segunda línea: tamaño (entero)
Después del movimiento, el paquete original debe tener un tamaño de 0 y una suma de 0 (ya que sus datos fueron transferidos), mientras que el paquete transferido debe tener todos los datos originales. Esto demuestra cómo la semántica de movimiento evita las costosas copias profundas al transferir simplemente la propiedad del puntero.
Incluye tu archivo de encabezado en main.cpp usando #include "DataPacket.h" y no olvides incluir <utility> para std::move().
Pruébalo tú mismo
#include <iostream>
#include <string>
#include <utility>
#include "DataPacket.h"
using namespace std;
int main() {
// Leer entrada
string packetId;
int size;
cin >> packetId;
cin >> size;
// TODO: Crear un DataPacket llamado 'original' con los valores de entrada
// TODO: Imprimir "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: Crear un nuevo paquete llamado 'transferred' moviéndolo desde original
// Sugerencia: Usar std::move()
// TODO: Imprimir "After move:"
// TODO: Imprimir "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: Imprimir "Transferred - ID: <id>, Size: <size>, Sum: <sum>"
return 0;
}Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genéricoPractica por tu cuenta: Compilador de C++ online