Menu
Coddy logo textTech

Semántica de movimiento y Rvalues

Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 83 de 104.

En C++, cada expresión es o bien un lvalue (tiene una identidad persistente, puede ser direccionado) o un rvalue (temporal, a punto de ser destruido). Comprender esta distinción desbloquea la move semantics: una potente optimización que evita copias innecesarias.

Una referencia rvalue, declarada con &&, se vincula específicamente a objetos temporales. Esto te permite "robar" recursos de objetos que están a punto de desaparecer de todos modos:

#include <iostream>
#include <utility>

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : data(new int[s]), size(s) {
        std::cout << "Constructed\n";
    }
    
    // Constructor de movimiento - roba recursos
    Buffer(Buffer&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;  // Deja el origen en un estado válido
        other.size = 0;
        std::cout << "Moved\n";
    }
    
    ~Buffer() { delete[] data; }
};

int main() {
    Buffer b1(1000);
    Buffer b2(std::move(b1));  // Activa el constructor de movimiento
}

La función std::move en realidad no mueve nada; simplemente convierte un lvalue en una referencia rvalue, indicando que estás dispuesto a renunciar a los recursos del objeto. El movimiento real ocurre en el constructor de movimiento o en el operador de asignación de movimiento.

La semántica de movimiento mejora drásticamente el rendimiento cuando se trabaja con objetos pesados en recursos, como contenedores o cadenas. En lugar de realizar copias profundas de megabytes de datos, simplemente se transfiere la propiedad del puntero, una operación de tiempo constante independientemente del tamaño.

challenge icon

Desafío

Fácil

Vamos a construir una clase DataBuffer que gestione recursos y demuestre la semántica de movimiento en acción. Verás cómo mover recursos en lugar de copiarlos puede mejorar drásticamente la eficiencia al transferir la propiedad de la memoria asignada dinámicamente.

Organizarás tu código en tres archivos:

  • DataBuffer.h: Define tu clase DataBuffer que gestiona un array de enteros asignado dinámicamente.

    Tu clase debe tener miembros privados para el puntero de datos (int*), el tamaño (size_t) y un nombre (std::string) para ayudar a rastrear qué búfer es cuál durante las operaciones.

    Declara lo siguiente:

    • Un constructor que reciba un nombre std::string y un tamaño size_t, asigne el array e imprima: [name] constructed with size [size]
    • Un constructor de movimiento que reciba una referencia rvalue, robe los recursos e imprima: [name] moved from [source_name] (donde el búfer de destino toma el nombre del origen)
    • Un destructor que imprima [name] destroyed (o empty destroyed si el búfer fue el origen de un movimiento)
    • Un método getSize() que devuelva el tamaño actual
    • Un método getName() que devuelva el nombre del búfer

    Recuerda marcar tu constructor de movimiento como noexcept y dejar el objeto de origen en un estado vacío válido (nullptr, tamaño 0, nombre "empty").

  • DataBuffer.cpp: Implementa todos los métodos declarados en tu cabecera. Cuando se ejecute el destructor, solo elimina los datos si el puntero no es nulo. Incluye <iostream> para la salida.
  • main.cpp: Lee dos entradas:
    1. Un nombre para tu búfer (string)
    2. Un tamaño para tu búfer (integer)

    Crea un DataBuffer con el nombre y tamaño dados. Luego crea un segundo búfer moviendo el primero usando std::move(). Después del movimiento, imprime el estado de ambos búferes:

    • Original: [name] size=[size]
    • New: [name] size=[size]

    Incluye <utility> para std::move.

Por ejemplo, con las entradas Alpha y 100:

Alpha constructed with size 100
Alpha moved from Alpha
Original: empty size=0
New: Alpha size=100
Alpha destroyed
empty destroyed

Con las entradas Buffer y 50:

Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyed

Observa cómo el constructor de movimiento transfiere la propiedad de la memoria asignada sin copiar ningún dato. El búfer original queda en un estado vacío pero válido, y cuando ambos búferes se destruyen al final del programa, solo el que aún posee la memoria la elimina realmente.

Hoja de referencia

Las expresiones en C++ son lvalues (persistentes, direccionables) o rvalues (temporales). La semántica de movimiento optimiza el rendimiento transfiriendo recursos de objetos temporales en lugar de copiarlos.

Referencias Rvalue

Declaradas con &&, las referencias rvalue se vinculan a objetos temporales:

Buffer(Buffer&& other)  // Parámetro de referencia rvalue

Constructor de movimiento

Roba recursos de un objeto temporal. Márcalo como noexcept y deja el origen en un estado vacío válido:

Buffer(Buffer&& other) noexcept 
    : data(other.data), size(other.size) {
    other.data = nullptr;  // Dejar el origen válido
    other.size = 0;
}

std::move

Convierte un lvalue en una referencia rvalue, señalando la disposición a transferir recursos. Incluye <utility>:

Buffer b1(1000);
Buffer b2(std::move(b1));  // Activa el constructor de movimiento

std::move no mueve nada por sí mismo; la transferencia real ocurre en el constructor de movimiento o en el operador de asignación de movimiento.

Beneficios

La semántica de movimiento proporciona una transferencia de recursos en tiempo constante independientemente del tamaño del objeto, evitando copias profundas costosas de estructuras de datos grandes.

Pruébalo tú mismo

#include <iostream>
#include <string>
#include <utility>
#include "DataBuffer.h"

int main() {
    std::string name;
    int size;
    
    std::cin >> name;
    std::cin >> size;
    
    // TODO: Crear un DataBuffer con el name y size dados
    
    // TODO: Crear un segundo buffer moviendo el primero usando std::move()
    
    // TODO: Imprimir el estado de ambos buffers:
    // Original: [name] size=[size]
    // Nuevo: [name] size=[size]
    
    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