En el lenguaje de programación C++ , un constructor de copia es un constructor especial para crear un nuevo objeto como copia de un objeto existente. Los constructores de copia son la forma estándar de copiar objetos en C++, a diferencia de la clonación , y presentan particularidades propias de C++.
El primer argumento de dicho constructor es una referencia a un objeto del mismo tipo que el que se está construyendo (constante o no constante), al que pueden seguir parámetros de cualquier tipo (todos con valores predeterminados).
Normalmente, el compilador crea automáticamente un constructor de copia para cada clase (conocido como constructor de copia implícito ), pero en casos especiales, el programador crea dicho constructor, denominado constructor de copia definido por el usuario . En estos casos, el compilador no crea uno. Por lo tanto, siempre existe un constructor de copia, ya sea definido por el usuario o por el sistema.
Generalmente se necesita un constructor de copia definido por el usuario cuando un objeto posee punteros o referencias no compartibles , como a un archivo , en cuyo caso también se deben escribir un destructor y un operador de asignación (ver Regla de tres ).
Definición
La copia de objetos se logra mediante el uso de un constructor de copia y un operador de asignación . Un constructor de copia tiene como primer parámetro una referencia (posiblemente constante o volátil ) a su propio tipo de clase. Puede tener más argumentos, pero el resto debe tener valores predeterminados asociados. [ 1 ] Los siguientes serían constructores de copia válidos para la clase :X
X ( const X & copy_from_me ); X ( X & copy_from_me ); X ( volatile X & copy_from_me ); X ( const volatile X & copy_from_me ); X ( X & copy_from_me , int = 0 ); X ( const X & copy_from_me , double = 1.0 , int = 42 ); ...Se debe usar la primera opción a menos que haya una buena razón para usar alguna de las otras. Una de las diferencias entre la primera y la segunda es que con la primera se pueden copiar objetos temporales. Por ejemplo:
X a = X (); // válido dado X(const X& copy_from_me) pero no válido dado X(X& copy_from_me)// porque el segundo quiere una X& no constante// Para crear un objeto, el compilador primero crea un objeto temporal invocando el constructor predeterminado.// de X, luego usa el constructor de copia para inicializar como una copia de ese temporal.// Los objetos temporales creados durante la ejecución del programa siempre son de tipo const. Por lo tanto, se requiere la palabra clave const.// Para algunos compiladores ambas versiones funcionan, pero no se debe confiar en este comportamiento.// porque no es estándar.Una diferencia similar se aplica al intentar copiar directamente un constobjeto:
const X a ;X b = a ; // válido dado X(const X& copy_from_me) pero no válido dado X(X& copy_from_me)// porque el segundo quiere una X& no constanteLa X&forma del constructor de copia se utiliza cuando es necesario modificar el objeto copiado. Esto es muy raro, pero se puede ver su uso en la biblioteca estándar std::auto_ptr. Se debe proporcionar una referencia:
X a ;X b = a ; // válido si se define alguno de los constructores de copia// ya que se está pasando una referencia.Los siguientes son constructores de copia no válidos porque copy_from_meno se pasa como referencia ( &) :
X ( X copia_de_mí ); X ( const X copia_de_mí );porque la llamada a esos constructores también requeriría una copia, lo que daría como resultado una llamada recursiva infinita.
Los siguientes casos pueden dar lugar a una llamada a un constructor de copia:
- Cuando un objeto es devuelto por valor
- Cuando un objeto se pasa (a una función) por valor como argumento
- Cuando se lanza un objeto
- Cuando un objeto es capturado por valor
- Cuando un objeto se coloca en una lista de inicialización encerrada entre llaves.
Estos casos se denominan colectivamente inicialización por copia y son equivalentes a: [ 2 ]T x = a;
Sin embargo, no está garantizado que se llame a un constructor de copia en estos casos, porque el estándar de C++ permite que el compilador optimice la copia en ciertos casos, un ejemplo de ello es la optimización del valor de retorno (a veces denominada RVO).
Operación
Se puede asignar un valor a un objeto utilizando una de las dos técnicas siguientes:
- Asignación explícita en una expresión
- Inicialización
Asignación explícita en una expresión
Objeto a ; Objeto b ; a = b ; // se traduce como Object::operator=(const Object&), por lo tanto, se llama a.operator=(b) // (¡invoca una copia simple, no un constructor de copia!)Inicialización
Un objeto puede inicializarse de cualquiera de las siguientes maneras.
- Mediante declaración: se traduce como (invocar constructor de copia)
Objectb=a;Object::Object(constObject&) - Mediante argumentos de función:
typefunction(Objecta); - A través del valor de retorno de la función:
Objecta=function();
El constructor de copia se utiliza únicamente para inicializaciones y no se aplica a las asignaciones, donde en su lugar se utiliza el operador de asignación.
El constructor de copia implícito de una clase llama a los constructores de copia base y copia sus miembros mediante los métodos apropiados a su tipo. Si es un tipo de clase, se llama al constructor de copia. Si es un tipo escalar, se utiliza el operador de asignación integrado. Finalmente, si es un array, cada elemento se copia de la manera apropiada a su tipo. [ 3 ]
Mediante el uso de un constructor de copia definido por el usuario, el programador puede definir el comportamiento que se realizará cuando se copie un objeto.
Ejemplos
Estos ejemplos ilustran cómo funcionan los constructores de copia y por qué a veces son necesarios.
Constructor de copia implícito
![]()
Consideremos el siguiente ejemplo:
importar std ;struct Persona { int edad ;Persona explícita ( int edad ) : edad ( edad ) {} };int main () { Persona timmy ( 10 ); Persona sally ( 15 );Persona timmy_clone = timmy ; std :: println ( " Timmy edad: {}, Sally edad: {}, Timmy clone edad: {}" , timmy.age, sally.age, timmy_clone.age); timmy.age = 23 ; std :: println ( " Timmy edad : { } , Sally edad : { } , Timmy clone edad : { } " , timmy.age , sally.age , timmy_clone.age ) ; }10 15 10 23 15 10Como era de esperar, timmyse ha copiado al nuevo objeto timmy_clone. Si bien timmyla edad de cambió, timmy_clonela edad de permaneció igual. Esto se debe a que son objetos totalmente diferentes.
El compilador ha generado un constructor de copia para nosotros, y podría escribirse de esta manera:
Persona ( const Persona & otro ) : edad { otro . edad } /* Llama al constructor de copia de la edad. */ {}Constructor de copia definido por el usuario
Consideremos una clase de matriz dinámica muy simple como la siguiente:
importar std ;clase IntArray {std :: size_t tamaño ;entero * datos ;público :IntArray explícito ( tamaño de entero ) :tamaño { tamaño }, datos { nuevo int [ tamaño ]} {}~ IntArray () {si ( datos ) {eliminar [] datos ;}}[[ no descartar ]]std :: size_t getSize () const noexcept {tamaño de retorno ;}operador entero []( std :: size_t índice ) const noexcept {devolver datos [ índice ];}};int main () {IntArray primero ( 20 );primero [ 0 ] = 25 ;{Copia de IntArray = primero ;std :: println ( "first[0] = {}, copy[0] = {}" , first [ 0 ], copy [ 0 ]);} // (1)primero [ 0 ] = 10 ; // (2)}25 25 Fallo de segmentaciónComo no especificamos un constructor de copia, el compilador generó uno por nosotros. El constructor generado se vería algo así:
El problema con este constructor es que realiza una copia superficial del puntero de datos . Solo copia la dirección del miembro de datos original; esto significa que ambos comparten un puntero al mismo bloque de memoria, lo cual no es lo que queremos. Cuando el programa llega a la línea (1) , se llama al destructor de la copia (porque los objetos en la pila se destruyen automáticamente cuando termina su ámbito). El destructor del array elimina el array de datos del original, por lo tanto, cuando eliminó los datos de la copia , como comparten el mismo puntero, también eliminó los datos del primero . La línea (2) ahora accede a datos no válidos y escribe en ellos. Esto produce un fallo de segmentación .
Si escribimos nuestro propio constructor de copia que realice una copia profunda , este problema desaparece.
IntArray ( const IntArray & other ) :tamaño { otro . tamaño }, datos { nuevo int [ otro . tamaño ]} {std :: copy ( other.data , other.data + other.size , data ) ;}Aquí, creamos un nuevo array de enteros y copiamos su contenido. Ahora, el destructor de `other` solo borra sus propios datos, no los de `first` . La línea (2) ya no producirá un error de segmentación.
En lugar de realizar una copia profunda de inmediato, existen algunas estrategias de optimización que se pueden utilizar. Estas permiten compartir de forma segura los mismos datos entre varios objetos, ahorrando así espacio. La estrategia de copia en escritura realiza una copia de los datos solo cuando se escribe en ellos. El conteo de referencias mantiene el conteo de cuántos objetos hacen referencia a los datos y los eliminará solo cuando este conteo llegue a cero (por ejemplo boost::shared_ptr).
Constructor de copia bit a bit
En C++ no existe el concepto de "constructor de copia bit a bit". Sin embargo, el constructor de copia generado por defecto copia invocando constructores de copia en los miembros, y para un miembro puntero sin formato, esto copia el puntero sin formato (es decir, no es una copia profunda).
Constructor de copia lógico

Un constructor de copia lógico crea una copia exacta de la estructura, así como de sus estructuras dinámicas. Los constructores de copia lógicos se utilizan principalmente cuando el objeto que se está copiando contiene punteros u objetos complejos.
Constructor de copia explícito
Un constructor de copia explícito es aquel que se declara explícitamente mediante la palabra clave explicit . Por ejemplo:
X explícito ( const X & copiar_de_mí );Se utiliza para evitar la copia de objetos en las llamadas a funciones o con la sintaxis de inicialización por copia.
Véase también
Referencias
- ↑ INCITS ISO IEC 14882-2003 12.8.2.Archivado el 8 de junio de 2007 en Wayback Machine.
- ↑ ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §8.5 Inicializadores [dcl.init] párr. 12
- ↑ "INCITS ISO IEC 14882-2003 12.8.8" . Archivado del original el 8 de junio de 2007.
- ↑ Informática: Un enfoque estructurado con C++ por Behrouz A. Forouzan y Richard F. Gilberg, figura 10-9, página 507
- C++
- Método (programación informática)