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 copiaSi 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.
Desafío
FácilVamos 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 claseMessageLogque 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
capacitypara el número máximo de mensajes y uncountpara 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.
- Miembros privados: un puntero a un arreglo de strings para almacenar mensajes, una
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
MessageLogllamadooriginalcon la capacidad de entrada. - Añade ambos mensajes a
original. - Crea una copia llamada
backuputilizando el constructor de copia. - Añade un tercer mensaje
"New message"solo aoriginal. - Imprime
"Original count: <count>". - Imprime
"Backup count: <count>". - Imprime todos los mensajes de
backup, uno por línea.
- Crea un
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 copiaSi 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;
}
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
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico