Cargando...

Constructor de copia y operador de asignación en C++

Aprende constructores de copia y operadores de asignación en C++ para gestionar correctamente objetos y recursos.

En C++, cuando los objetos se copian o se asignan entre sí, entran en juego dos mecanismos especiales: el Constructor de Copia (Copy Constructor) y el Operador de Asignación (=). Ambos transfieren datos entre objetos, pero difieren en el momento de ejecución y en su propósito. En este artículo aprenderemos sus diferencias, ejemplos de uso y el concepto de copia profunda (deep copy).


1. ¿Qué es un Constructor de Copia?

El constructor de copia es un método especial que se llama cuando se crea un nuevo objeto a partir de otro ya existente, haciendo una copia del mismo.


class Book {
private:
    string *title;

public:
    Book(string t) {
        title = new string(t);
        cout << "Constructor llamado." << endl;
    }

    // Constructor de copia
    Book(const Book &other) {
        title = new string(*other.title); // Copia profunda
        cout << "Constructor de copia llamado." << endl;
    }

    ~Book() {
        delete title;
        cout << "Destructor llamado." << endl;
    }

    void Show() const { cout << "Libro: " << *title << endl; }
};

Uso:


int main() {
    Book b1("Programación en C++");
    Book b2 = b1; // Se llama al constructor de copia

    b1.Show();
    b2.Show();
}

Salida:


Constructor llamado.
Constructor de copia llamado.
Libro: Programación en C++
Libro: Programación en C++
Destructor llamado.
Destructor llamado.

En este caso, cada objeto tiene su propia área de memoria (copia profunda). Si solo se copiara la dirección (copia superficial), ambos objetos compartirían la misma memoria y al ejecutarse el destructor ocurriría un error de “doble eliminación”.


2. ¿Qué es el Operador de Asignación (=)?

El operador de asignación se utiliza para copiar los valores de un objeto a otro objeto que ya ha sido creado. Es decir, el objeto ya existe; solo se reemplaza su contenido.


class Book {
private:
    string *title;

public:
    Book(string t) {
        title = new string(t);
        cout << "Constructor llamado." << endl;
    }

    // Operador de asignación
    Book& operator=(const Book &other) {
        cout << "Operador de asignación llamado." << endl;

        // Comprobación de autoasignación
        if (this == &other)
            return *this;

        // Liberar la memoria antigua
        delete title;

        // Crear una nueva copia
        title = new string(*other.title);

        return *this;
    }

    ~Book() {
        delete title;
        cout << "Destructor llamado." << endl;
    }

    void Show() const { cout << "Libro: " << *title << endl; }
};

Uso:


int main() {
    Book b1("C++ Moderno");
    Book b2("Guía de Python");

    b2 = b1; // Se llama al operador de asignación
    b1.Show();
    b2.Show();
}

Salida:


Constructor llamado.
Constructor llamado.
Operador de asignación llamado.
Libro: C++ Moderno
Libro: C++ Moderno
Destructor llamado.
Destructor llamado.

Aquí, ambos objetos tienen direcciones de memoria diferentes, pero contienen los mismos datos. Los datos antiguos se eliminaron de forma segura y se reemplazaron por los nuevos.


3. Problema de Copia Superficial (Shallow Copy)

Si no se define un constructor de copia ni un operador de asignación, el compilador de C++ crea automáticamente una “copia superficial (shallow copy)”. En este caso, solo se copia la dirección, y los datos reales se comparten.


class ShallowBook {
public:
    string *title;

    ShallowBook(string t) {
        title = new string(t);
    }

    ~ShallowBook() {
        delete title;
    }
};

int main() {
    ShallowBook b1("C++ Primer");
    ShallowBook b2 = b1; // Copia superficial automática

    cout << *b1.title << endl;
    cout << *b2.title << endl; // ¡Misma área de memoria!

} // El destructor elimina dos veces → fallo

Por esta razón, en las clases que utilizan memoria dinámica, tanto el constructor de copia como el operador de asignación deben definirse manualmente.


4. Uso seguro con Copia Profunda (Deep Copy)

Una copia profunda duplica los datos reales en una nueva área de memoria. De esta manera, cada objeto gestiona su propia memoria de forma independiente.


class DeepBook {
private:
    string *title;

public:
    DeepBook(string t) {
        title = new string(t);
    }

    // Constructor de copia profunda
    DeepBook(const DeepBook &other) {
        title = new string(*other.title);
    }

    // Operador de asignación con copia profunda
    DeepBook& operator=(const DeepBook &other) {
        if (this == &other) return *this;
        delete title;
        title = new string(*other.title);
        return *this;
    }

    ~DeepBook() {
        delete title;
    }

    void Show() const { cout << *title << endl; }
};

int main() {
    DeepBook b1("Aprendiendo copia profunda");
    DeepBook b2 = b1; // Constructor de copia
    DeepBook b3("Vacío");
    b3 = b1; // Operador de asignación

    b1.Show();
    b2.Show();
    b3.Show();
}

Cada objeto gestiona su propia memoria; el destructor se llama de forma segura para cada uno de ellos.


5. Regla de Tres (Rule of Three)

Si una clase:

→ Entonces los tres deben definirse juntos. Si uno se define y los otros no, el compilador generará versiones por defecto automáticamente, lo que puede causar errores o comportamientos inesperados.

Desde C++11: se introdujo el concepto de la “Regla de Cinco (Rule of Five)”, que también incluye el constructor de movimiento y el operador de asignación por movimiento.


6. Ejemplo práctico: Copia y asignación de libros

El siguiente ejemplo muestra cómo funcionan juntos el constructor de copia y el operador de asignación.


#include <iostream>
#include <string>
using namespace std;

class Book {
private:
    string *title;

public:
    Book(string t) {
        title = new string(t);
        cout << "Constructor: " << *title << endl;
    }

    // Constructor de copia
    Book(const Book &other) {
        title = new string(*other.title);
        cout << "Constructor de copia: " << *title << endl;
    }

    // Operador de asignación
    Book& operator=(const Book &other) {
        cout << "Operador de asignación: " << *other.title << endl;
        if (this == &other) return *this;
        delete title;
        title = new string(*other.title);
        return *this;
    }

    ~Book() {
        cout << "Destructor: " << *title << endl;
        delete title;
    }

    void Show() const { cout << *title << endl; }
};

int main() {
    Book b1("Fundamentos de C++");
    Book b2 = b1;      // Constructor de copia
    Book b3("Temporal");
    b3 = b1;           // Operador de asignación

    cout << "----- Salida -----" << endl;
    b1.Show();
    b2.Show();
    b3.Show();
}

Salida:


Constructor: Fundamentos de C++
Constructor de copia: Fundamentos de C++
Constructor: Temporal
Operador de asignación: Fundamentos de C++
----- Salida -----
Fundamentos de C++
Fundamentos de C++
Fundamentos de C++
Destructor: Fundamentos de C++
Destructor: Fundamentos de C++
Destructor: Fundamentos de C++

De esta manera, tanto la copia como la asignación de objetos se realizan de forma segura y controlada.


7. TL;DR

  • Constructor de copia → Crea un nuevo objeto copiando otro existente.
  • Operador de asignación → Asigna el contenido de un objeto existente a otro.
  • Sin una copia profunda, pueden producirse errores de “double delete”.
  • Regla de Tres → El destructor, el constructor de copia y el operador de asignación deben definirse juntos.
  • Regla de Cinco (C++11) → Incluye el constructor de movimiento y el operador de asignación por movimiento.
  • Todos los ejemplos pueden ejecutarse de forma segura en Visual Studio 2022 o GCC 11+.

Artículos relacionados