Menu
Coddy logo textTech

Sobrecarga del operador de asignación

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

El operador de asignación por copia (=) se llama cuando asignas un objeto existente a otro. A diferencia del constructor de copia (que crea un objeto nuevo), el operador de asignación trabaja con un objeto que ya existe y que puede tener recursos que necesiten ser liberados primero.

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    ~Buffer() { delete[] data; }
    
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {              // 1. Comprobación de autoasignación
            delete[] data;                  // 2. Limpiar el recurso existente
            size = other.size;              // 3. Copiar los datos
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;                       // 4. Devolver *this
    }
};

La comprobación de autoasignación (this != &other) es crítica. Sin ella, b = b; eliminaría los datos antes de intentar copiarlos, provocando un comportamiento indefinido.

El operador devuelve *this por referencia para permitir el encadenamiento como a = b = c;. Esto coincide con el comportamiento de los tipos integrados.

Para las clases que gestionan recursos, a menudo implementarás tanto los operadores de asignación de copia como los de movimiento:

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;           // Limpiar el recurso actual
        data = other.data;       // Robar el recurso
        size = other.size;
        other.data = nullptr;    // Dejar el origen en un estado válido
        other.size = 0;
    }
    return *this;
}

El operador de asignación por movimiento transfiere la propiedad en lugar de copiar, haciendo que operaciones como vec[0] = createBuffer(); sean mucho más eficientes.

challenge icon

Desafío

Fácil

Vamos a construir una clase DynamicString que gestione un arreglo de caracteres asignado dinámicamente y maneje adecuadamente la asignación entre objetos existentes. Este es un escenario práctico donde necesitas transferir datos de cadena de un objeto a otro de forma segura, sin fugas de memoria ni punteros colgantes.

Crearás dos archivos para organizar tu código:

  • DynamicString.h: Define una clase DynamicString que almacene una cadena de estilo C utilizando memoria dinámica. Tu clase debe gestionar:
    • Un puntero privado char* para los datos de la cadena
    • Un size_t privado para la longitud
    • Un constructor que reciba un const char* y cree una copia profunda (deep copy)
    • Un destructor que libere adecuadamente la memoria asignada
    • Un operador de asignación por copia que realice una copia profunda con protección contra la auto-asignación
    • Un operador de asignación por movimiento que transfiera la propiedad de manera eficiente
    • Un método c_str() que devuelva la cadena interna (const)
    • Un método length() que devuelva la longitud de la cadena (const)

    Tu operador de asignación por copia debe seguir los cuatro pasos esenciales: verificar la auto-asignación, limpiar los recursos existentes, copiar los nuevos datos y devolver *this. El operador de asignación por movimiento debe robar el puntero del origen y dejar al origen en un estado vacío válido (nullptr con longitud 0).

  • main.cpp: Lee tres cadenas de la entrada (cada una en una línea separada). Crea tres objetos DynamicString: str1 con la primera entrada, str2 con la segunda entrada y str3 con la tercera entrada.

    Demuestra tus operadores de asignación:

    1. Imprime el estado inicial de las tres cadenas
    2. Usa la asignación por copia: str1 = str2;
    3. Imprime str1 y str2 después de la asignación por copia (ambos deben tener el mismo contenido)
    4. Usa la asignación por movimiento: str2 = std::move(str3);
    5. Imprime str2 después de la asignación por movimiento, y la longitud de str3 (debe ser 0 después de haber sido movido)

    Formato de salida:

    Initial:
    str1: <value>
    str2: <value>
    str3: <value>
    After copy (str1 = str2):
    str1: <value>
    str2: <value>
    After move (str2 = std::move(str3)):
    str2: <value>
    str3 length: 0

Usa <cstring> para strlen y strcpy. Marca tu operador de asignación por movimiento como noexcept. Incluye <utility> en main.cpp para std::move. No olvides los protectores de inclusión (header guards) en tu archivo de cabecera.

Hoja de referencia

El operador de asignación por copia (=) asigna un objeto existente a otro. Debe encargarse de la limpieza de los recursos existentes antes de copiar los nuevos datos.

Estructura del Operador de Asignación por Copia

ClassName& operator=(const ClassName& other) {
    if (this != &other) {              // 1. Self-assignment check
        // 2. Clean up existing resources
        // 3. Copy data from other
    }
    return *this;                       // 4. Return *this
}

Ejemplo con Memoria Dinámica

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            delete[] data;                  // Clean up existing resource
            size = other.size;
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;
    }
};

La comprobación de auto-asignación (this != &other) evita que se eliminen los datos antes de copiarlos en casos como b = b;.

Devolver *this por referencia permite el encadenamiento: a = b = c;.

Operador de Asignación por Movimiento

El operador de asignación por movimiento transfiere la propiedad en lugar de copiar, mejorando la eficiencia:

ClassName& operator=(ClassName&& other) noexcept {
    if (this != &other) {
        // Clean up current resource
        // Steal the resource from other
        // Leave other in valid state (nullptr, 0, etc.)
    }
    return *this;
}

Ejemplo de Asignación por Movimiento

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;
        data = other.data;       // Steal the resource
        size = other.size;
        other.data = nullptr;    // Leave source valid
        other.size = 0;
    }
    return *this;
}

Marque la asignación por movimiento como noexcept para un rendimiento óptimo con los contenedores estándar.

Pruébalo tú mismo

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

using namespace std;

int main() {
    // Lee tres cadenas de la entrada
    string input1, input2, input3;
    getline(cin, input1);
    getline(cin, input2);
    getline(cin, input3);

    // Crea tres objetos DynamicString
    DynamicString str1(input1.c_str());
    DynamicString str2(input2.c_str());
    DynamicString str3(input3.c_str());

    // TODO: Imprime el estado inicial de las tres cadenas
    // Formato:
    // Initial:
    // str1: <valor>
    // str2: <valor>
    // str3: <valor>

    // TODO: Usa la asignación por copia: str1 = str2;

    // TODO: Imprime str1 y str2 después de la asignación por copia
    // Formato:
    // After copy (str1 = str2):
    // str1: <valor>
    // str2: <valor>

    // TODO: Usa la asignación por movimiento: str2 = std::move(str3);

    // TODO: Imprime str2 después del movimiento, y la longitud de str3
    // Formato:
    // After move (str2 = std::move(str3)):
    // str2: <valor>
    // str3 length: 0

    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