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 de un objeto temporal en lugar de copiarlos. Mientras que un constructor de copia duplica los datos, un constructor de movimiento "roba" los recursos, dejando al 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 ser destruido de todos modos.
El constructor de movimiento toma una referencia 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 la copia: no asignamos nueva memoria. Simplemente tomamos la propiedad de la memoria existente y establecemos el puntero del origen a 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 usa 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ácilVamos a construir 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 gestiona un array de bytes asignado dinámicamente, e implementarás un constructor de movimiento que "robe" los datos en lugar de copiarlos.
Crearás dos archivos para organizar tu código:
DataPacket.h: Define una claseDataPacketque gestiona una carga útil (payload) de datos enteros. Tu clase debe tener:- Miembros privados: un puntero a un array de enteros (
payload), unsizepara el número de elementos y unpacketId(string) para identificar el paquete - Un constructor parametrizado que recibe un ID de paquete y un tamaño, asigna el array y lo llena con valores de 0 a size-1. Imprime
"Packet <id> created with size <size>" - Un constructor de movimiento marcado como
noexceptque transfiere la propiedad del payload desde el objeto origen. Imprime"Packet <id> moved". Recuerda dejar el origen en un estado vacío válido (nullptr, size 0) - Un destructor que libera la memoria si el puntero no es nulo, e imprime
"Packet <id> destroyed" - Un método
getSize()que devuelve el tamaño actual - Un método
getId()que devuelve el ID del paquete - Un método
getSum()que devuelve la suma de todos los elementos en el payload (devuelve 0 si el payload es nulo)
- Miembros privados: un puntero a un array de enteros (
main.cpp: Demuestra la construcción por movimiento leyendo un ID de paquete y un tamaño de la entrada. Luego:- Crea un
DataPacketllamadooriginalcon los valores de entrada - Imprime
"Original - ID: <id>, Size: <size>, Sum: <sum>" - Crea un nuevo paquete llamado
transferredmoviendo desdeoriginalusandostd::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 (string)
- Segunda línea: tamaño (entero)
Después del movimiento, el paquete original debe tener tamaño 0 y suma 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 copias profundas costosas simplemente transfiriendo la propiedad del puntero.
Incluye tu archivo de cabecera en main.cpp usando #include "DataPacket.h" y no olvides incluir <utility> para std::move().
Hoja de referencia
Un constructor de movimiento transfiere recursos de un objeto temporal en lugar de copiarlos, evitando copias profundas costosas. Toma una referencia rvalue (&&) y "roba" los recursos, dejando al objeto de origen en un estado válido pero vacío.
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; }
};Los constructores de movimiento se llaman al inicializar desde objetos temporales o al usar std::move():
Buffer createBuffer() {
return Buffer(1000); // Se usa el constructor de movimiento (valor de retorno)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Movimiento explícito - b1 ahora está vacíoMarque los constructores de movimiento como noexcept cuando sea posible para habilitar optimizaciones en los contenedores estándar.
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
// Hint: 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érico