Copiar constructor

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 31 de marzo de 2015; las comprobaciones requieren 16 ediciones .

Un constructor de copias es un  constructor especial en el lenguaje de programación C++ y en algunos otros lenguajes de programación, como Java , que se usa para crear un nuevo objeto como una copia de uno existente. Tal constructor toma al menos un argumento: una referencia al objeto que se va a copiar.

Normalmente , el compilador generará automáticamente un constructor de copia para cada clase (conocido como constructores de copia implícitos , es decir, constructores de copia que se especifican implícitamente), pero en algunos casos, el programador creará un constructor de copia, que luego se denomina constructor explícito . copiar constructor (o "copiar constructor especificado explícitamente"). manera"). En tales casos, el compilador no genera constructores implícitos.

El constructor de copias se necesita principalmente cuando el objeto tiene un puntero o una referencia no compartida , como un archivo , en cuyo caso, por lo general, también necesitará un destructor y un operador de asignación (consulte la Regla de tres ).

Definición

La copia de objetos se realiza mediante el constructor de copia y el operador de asignación . El constructor de copias toma como primer parámetro (con el modificador de tipo const o volatile opcional) una referencia a su propio tipo de clase. Además de este parámetro, puede tener más parámetros adicionales, siempre que dichos parámetros adicionales se establezcan en valores predeterminados [1] . El siguiente ejemplo muestra constructores de copia válidos para la clase X:

X ( constante X & ); X ( X & ); X ( constante volátil X & ); X ( volátil X & ); X ( const X & , int = 10 ); X ( const X & , doble = 1.0 , int = 40 );

La primera entrada del constructor de copias es primaria; se deben usar otras formas solo cuando sea necesario. Solo puede copiar objetos temporales usando el primer constructor. Por ejemplo:

Xa = X ( ); // Se compilará si se implementa el constructor X(const X&), y arrojará un error // si solo se define X(X&). // Para crear un objeto a, el compilador creará un objeto temporal de clase // X y luego usará el constructor de copia para crear un objeto a. // Copiar objetos temporales requiere un tipo const.

En el ejemplo a continuación, el objeto a se crea como inmutable, por lo que al crear el objeto b , se requiere el constructor de la primera copia.

constante X a ; Xb = un ; _ // correcto si hay X(const X&) y no correcto si hay X(X&) // ya que el segundo no soporta el tipo const X&

El X&tipo de constructor de copia se usa cuando es necesario cambiar el objeto que se copia. Esta es una situación bastante rara, pero se proporciona en la biblioteca estándar llamando a std::auto_ptr. El enlace debe implementar:

Xa ; _ Xb = un ; _ // correcto si alguno de los constructores de copia está definido // ya que se pasó la referencia

Los siguientes constructores de copia (o constructores constantes) no son válidos:

X ( X ); X ( constante X );

ya que llamar a estos constructores requerirá otra copia, lo que conducirá a una llamada recursiva infinita (es decir, un bucle infinito).

Hay cuatro casos de llamar a un constructor de copias:

  1. Cuando un objeto es un valor de retorno
  2. Cuando se pasa un objeto (a una función) por valor como argumento
  3. Cuando un objeto se construye a partir de otro objeto (de la misma clase)
  4. Cuando el compilador genera un objeto temporal (como en el primer y segundo caso anteriores, como una conversión explícita, etc.)

Operaciones

A un objeto se le puede asignar un valor de una de dos maneras:

  • Asignación explícita en una expresión
  • Inicialización

Asignación explícita en una expresión

Objeto A ; Objeto B ; A = B ; // traducido como Object::operator=(const Object&), // llamando así a A.operator=(B)

Inicialización

Un objeto se puede inicializar de cualquiera de las siguientes formas:

una. Inicialización en la declaración

Objeto B = A ; // traducido como Objeto::Objeto(const Objeto&)

b. Inicialización al pasar argumentos a funciones

tipo función ( Objeto a );

C. Al devolver un valor de función

Objeto a = función ();

El constructor de copia se usa solo en el caso de la inicialización y no se usa en lugar de una asignación explícita (es decir, donde se usa el operador de asignación ).

El constructor de copia de clase implícito llama a los constructores de copia de las clases base y hace copias bit a bit de los miembros de la clase. Si un miembro de clase es una clase, se llama a su constructor de copia. Si es un tipo escalar (tipo POD en C++), se usa el operador de asignación integrado. Y finalmente, si es una matriz, cada elemento de la matriz se copia de la manera adecuada para su tipo. [2]

Mediante el uso de un constructor de copia explícito, el programador puede determinar qué hacer después de que se haya copiado el objeto.

Ejemplos

Los siguientes ejemplos ilustran cómo funcionan los constructores de copia y por qué son necesarios.

El constructor de copia implícito

#incluir <iostream> persona de clase { público : edad int ; Persona ( int edad ) : edad ( edad ) {} }; int principal () { persona Timmy ( 10 ); persona Sally ( 15 ); Persona timmy_clone = timmy ; std :: cout << timmy . edad << " " << sally . edad << " " << timmy_clone . edad << std :: endl ; timo _ edad = 23 ; std :: cout << timmy . edad << " " << sally . edad << " " << timmy_clone . edad << std :: endl ; }

Resultado

10 15 10 23 15 10

Como era de esperar, timmy se copió en el nuevo objeto timmy_clone . Al cambiar la edad (age) de timmy , la edad de timmy_clone no cambió: los objetos son completamente independientes.

El compilador generó un constructor de copia para nosotros, que podría escribirse así:

Persona ( Persona const & copia ) : edad ( copia . edad ) {}

Constructor de copia explícita

El siguiente ejemplo muestra una clase de matriz dinámica simple:

#incluir <iostream> matriz de clase { público : tamaño interior ; _ int * datos ; Matriz ( tamaño int ) : tamaño ( tamaño ), datos ( nuevo int [ tamaño ]) {} ~ Matriz () { eliminar [] datos ; } }; int principal () { Array primero ( 20 ); primero _ datos [ 0 ] = 25 ; { Copia de matriz = primero ; std :: cout << primero . datos [ 0 ] << " " << copiar . datos [ 0 ] << std :: endl ; } // (1) primero . datos [ 0 ] = 10 ; // (2) }

Resultado

25 25 Fallo de segmentación

Aquí el compilador generó el constructor de copias automáticamente. Este constructor se ve así:

Array ( Array const & copy ) : tamaño ( copia . tamaño ), datos ( copia . datos ) {}

El problema con este constructor es que hace una simple copia del puntero de datos . Solo copia la dirección, no los datos en sí. Y cuando el programa llega a la línea (1) , se llama al destructor de copias (los objetos en la pila se destruyen automáticamente cuando alcanzan sus límites). Como puede ver, el Array destructor elimina la matriz de datos , por lo que cuando elimina los datos de la copia , también elimina los primeros datos . La línea (2) ahora recibe datos incorrectos y los escribe. Esto conduce a la famosa falla de segmentación .

En el caso de un constructor de copias nativo que realiza una copia profunda , este problema no se presentará:

Array ( Array const & copy ) : tamaño ( copia . tamaño ), datos ( new int [ copia . tamaño ]) { std :: copia ( copia . datos , copia . datos + copia . tamaño , datos ); // #incluye <algoritmo> para std::copy }

Aquí se crea una nueva matriz int y el contenido se copia en ella. Ahora el destructor de copias sólo eliminará sus datos y no tocará los datos del primero . La línea (2) ya no provoca un fallo de segmentación.

En lugar de realizar una copia profunda, se pueden utilizar varias estrategias de optimización. Esto permitirá el acceso a datos para múltiples objetos de forma segura, ahorrando así memoria. La estrategia de copia en escritura crea una copia de los datos solo cuando se escriben. El recuento de referencia contiene un contador del número de objetos que hacen referencia a los datos y solo lo elimina cuando el contador llega a cero (por ejemplo, boost::shared_ptr).

Copiar constructores y plantillas

El constructor de plantillas no es un constructor de copias. .

plantilla < nombre de tipo T > Array :: Array ( const T & copy ) : tamaño ( copia . tamaño ()), datos ( new int [ copia . tamaño ()]) { std :: copiar ( copiar . comenzar (), copiar . terminar (), datos ); }

Este constructor no se utilizará si T es de tipo Array.

matriz matriz ( 5 ) ; Array arr2 ( arr );

La segunda línea llamará al constructor de copia sin plantilla o, si no existe, al constructor de copia predeterminado.

Véase también

Notas

  1. INCITS ISO IEC 14882-2003 12.8.2. [1] Archivado el 8 de junio de 2007 en Wayback Machine .
  2. INCITS ISO IEC 14882-2003 12.8.8. [2] Archivado el 8 de junio de 2007 en Wayback Machine .