Menu
Coddy logo textTech

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ío

Marca 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.

challenge icon

Desafío

Fácil

Construyamos 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 clase DataPacket que administre una carga útil de datos enteros. Tu clase debe tener:
    • Miembros privados: un puntero a un arreglo de enteros (payload), un size para el número de elementos y un packetId (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 noexcept que 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)
  • main.cpp: Demuestra la construcción por movimiento leyendo un ID de paquete y un tamaño desde la entrada. Después:
    • Crea un DataPacket llamado original con los valores de entrada
    • Imprime "Original - ID: <id>, Size: <size>, Sum: <sum>"
    • Crea un paquete nuevo llamado transferred moviendo desde original mediante std::move()
    • Imprime "After move:"
    • Imprime "Original - ID: <id>, Size: <size>, Sum: <sum>"
    • Imprime "Transferred - ID: <id>, Size: <size>, Sum: <sum>"

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;
}
quiz iconPonte a prueba

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

Practica por tu cuenta: Compilador de C++ online