Menu
Coddy logo textTech

Constructor de copia

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

Un constructor de copia crea un nuevo objeto como una copia de un objeto existente. Se llama cuando inicializas un objeto a partir de otro objeto del mismo tipo, pasas un objeto por valor o devuelves un objeto por valor.

El constructor de copia toma una referencia constante a un objeto de la misma clase:

class Player {
    std::string name;
    int health;
public:
    Player(std::string n, int h) : name(n), health(h) {}
    
    // Constructor de copia
    Player(const Player& other) {
        name = other.name;
        health = other.health;
    }
};

Player original("Hero", 100);
Player copy = original;    // Se llama al constructor de copia
Player another(original);  // También llama al constructor de copia

Si no defines un constructor de copia, el compilador genera uno que realiza una copia superficial: copia el valor de cada miembro directamente. Esto funciona bien para tipos simples, pero causa problemas cuando tu clase gestiona memoria dinámica.

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
    }
    
    // Constructor de copia profunda
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];              // Asignar nueva memoria
        for (size_t i = 0; i < size; i++)
            data[i] = other.data[i];       // Copiar contenidos
    }
    
    ~Buffer() { delete[] data; }
};

Sin un constructor de copia personalizado, ambos objetos apuntarían a la misma memoria. Cuando uno se destruye, el otro tendría un puntero colgante. Una copia profunda asigna nueva memoria y copia los datos reales, asegurando que cada objeto posea sus propios recursos.

challenge icon

Desafío

Fácil

Vamos a construir un sistema de historial de mensajes que demuestre la importancia de la copia profunda (deep copying) cuando tu clase gestiona memoria dinámica. Crearás una clase MessageLog que almacena mensajes en un arreglo asignado dinámicamente e implementarás un constructor de copia adecuado para asegurar que cada copia tenga su propia memoria independiente.

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

  • MessageLog.h: Define una clase MessageLog que gestiona una colección de mensajes utilizando memoria dinámica. Tu clase debe tener:
    • Miembros privados: un puntero a un arreglo de strings para almacenar mensajes, una capacity para el número máximo de mensajes y un count para el número actual de mensajes.
    • Un constructor parametrizado que recibe una capacidad, asigna el arreglo en el heap e inicializa el count a 0.
    • Un constructor de copia que realiza una copia profunda — asignando nueva memoria y copiando todos los mensajes del objeto origen.
    • Un destructor que libera la memoria asignada e imprime "MessageLog destroyed".
    • Un método addMessage(string msg) que añade un mensaje si hay espacio.
    • Un método getCount() que devuelve el conteo actual de mensajes.
    • Un método getMessage(int index) que devuelve el mensaje en el índice dado.
  • main.cpp: Demuestra que tu constructor de copia crea una copia independiente. Lee un valor de capacidad y dos mensajes de la entrada, luego:
    • Crea un MessageLog llamado original con la capacidad de entrada.
    • Añade ambos mensajes a original.
    • Crea una copia llamada backup utilizando el constructor de copia.
    • Añade un tercer mensaje "New message" solo a original.
    • Imprime "Original count: <count>".
    • Imprime "Backup count: <count>".
    • Imprime todos los mensajes de backup, uno por línea.

El formato de entrada será:

  • Primera línea: capacidad (entero)
  • Segunda línea: primer mensaje (string)
  • Tercera línea: segundo mensaje (string)

Si tu copia profunda está implementada correctamente, el backup debería tener 2 mensajes mientras que el original tiene 3 — demostrando que tienen memoria independiente. ¡Sin un constructor de copia adecuado, ambos compartirían el mismo arreglo y los cambios en uno afectarían al otro!

Incluye tu archivo de cabecera en main.cpp usando #include "MessageLog.h".

Hoja de referencia

Un constructor de copia crea un nuevo objeto como una copia de un objeto existente. Toma una referencia constante a un objeto de la misma clase:

class Player {
    std::string name;
    int health;
public:
    Player(std::string n, int h) : name(n), health(h) {}
    
    // Constructor de copia
    Player(const Player& other) {
        name = other.name;
        health = other.health;
    }
};

Player original("Hero", 100);
Player copy = original;    // Se llama al constructor de copia
Player another(original);  // También llama al constructor de copia

Si no defines un constructor de copia, el compilador genera uno que realiza una copia superficial (shallow copy), copiando el valor de cada miembro directamente. Esto causa problemas cuando tu clase gestiona memoria dinámica.

Un constructor de copia profunda (deep copy constructor) asigna nueva memoria y copia los datos reales, asegurando que cada objeto posea sus propios recursos:

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
    }
    
    // Constructor de copia profunda
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];              // Asignar nueva memoria
        for (size_t i = 0; i < size; i++)
            data[i] = other.data[i];       // Copiar contenidos
    }
    
    ~Buffer() { delete[] data; }
};

Sin un constructor de copia personalizado, ambos objetos apuntarían a la misma memoria, lo que causaría punteros colgantes (dangling pointers) cuando uno de ellos sea destruido.

Pruébalo tú mismo

#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;

int main() {
    // Leer entrada
    int capacity;
    cin >> capacity;
    cin.ignore();
    
    string msg1, msg2;
    getline(cin, msg1);
    getline(cin, msg2);

    // TODO: Crear un MessageLog llamado 'original' con la capacidad de entrada

    // TODO: Añadir ambos mensajes a original

    // TODO: Crear una copia llamada 'backup' usando el constructor de copia

    // TODO: Añadir "New message" solo a original

    // TODO: Imprimir "Original count: <count>"

    // TODO: Imprimir "Backup count: <count>"

    // TODO: Imprimir todos los mensajes de backup, uno por línea

    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