11.3.3. El constructor de copia por defecto

Como el constructor de copia implementa el paso y retorno por valor, es importante que el compilador cree uno en el caso de estructuras simples (de hecho, es lo mismo que hace C). Sin embargo todo lo que se ha visto es el comportamiento por defecto: una copia bit a bit.

Cuando se utilizan tipos más complejos, el compilador de C++ creará un constructor de copia automáticamente si no se implementa explícitamente. De nuevo, una copia bit a bit no tiene sentido, porque no tiene porqué ser el comportamiento que se necesita.

He aquí un ejemplo para mostrar el comportamiento más inteligente del compilador. Suponga que crea una nueva clase compuesta por objetos de varias clases diferentes. A esto se le denomina composición, y es una de las formas en las que se pueden hacer nuevas clases a partir de las ya existentes. Ahora desempeñe el papel de un novato que trata de resolver un problema rápidamente creando una nueva clase de esta manera. No sabe nada sobre los constructores de copia, así que no lo implementa. El ejemplo muestra lo que el compilador hace cuando crea un constructor de copia por defecto para su nueva clase:

//: C11:DefaultCopyConstructor.cpp
// Automatic creation of the copy-constructor
#include <iostream>
#include <string>
using namespace std;

class WithCC { // With copy-constructor
public:
  // Explicit default constructor required:
  WithCC() {}
  WithCC(const WithCC&) {
    cout << "WithCC(WithCC&)" << endl;
  }
};

class WoCC { // Without copy-constructor
  string id;
public:
  WoCC(const string& ident = "") : id(ident) {}
  void print(const string& msg = "") const {
    if(msg.size() != 0) cout << msg << ": ";
    cout << id << endl;
  }
};

class Composite {
  WithCC withcc; // Embedded objects
  WoCC wocc;
public:
  Composite() : wocc("Composite()") {}
  void print(const string& msg = "") const {
    wocc.print(msg);
  }
};

int main() {
  Composite c;
  c.print("Contents of c");
  cout << "Calling Composite copy-constructor"
       << endl;
  Composite c2 = c;  // Calls copy-constructor
  c2.print("Contents of c2");
} ///:~

Listado 11.9. C11/DefaultCopyConstructor.cpp


La clase WithCC contiene un constructor de copia, que simplemente anuncia que ha sido llamado, y esto demuestra una cuestión interesante: dentro de la clase Composite se crea un objeto tipo WithCC utilizando el constructor por defecto. Si WithCC no tuviera ningún constructor, el compilador crearía uno por defecto automáticamente, el cual, en este caso, no haría nada. No obstante, si añade un constructor por defecto, le está diciendo al compilador que ha de utilizar los constructores disponibles, por lo que él no crea ningún constructor por defecto y se quejará a no ser que explícitamente cree un constructor por defecto, como se hizo en WithCC.

La clase WoCC no tiene constructor de copia, pero su constructor almacenará un string interno imprimible por la función print(). La lista de inicialización del constructor (presentado brevemente en el Capítulo 8 y tratado completamente en el Capítulo 14) de Composite llama explícitamente a este constructor. La razón de esto se verá posteriormente.

La clase Composite tiene objetos miembro tanto de WithCC como de WoCC (note que el objeto interno wocc se inicializa en la lista de inicializadores del constructor de Composite, como debe ser), pero no están inicializados explícitamente en el constructor de copia. Sin embargo un objeto Composite se crea en main() utilizando el constructor de copia:

Composite c2 = c;

El compilador ha creado un constructor de copia para Composite automáticamente, y la salida del programa revela la manera en que se crea:

Contents of c: Composite()
Calling Composite copy-constructor
WithCC(WithCC&)
Contents of c2: Composite()

Para la creación de un constructor de copia para una clase que utiliza composición (y herencia, que se trata en el Capítulo 14), el compilador llama a todos los constructores de copia de todos los miembros objeto y de las clases base de manera recursiva. Es decir, si el miembro también contiene otro objeto, también se llama a su constructor de copia. En el ejemplo, el compilador llama al constructor de copia de WithCC. La salida muestra que se llama a este constructor. Como WoCC no tiene constructor de copia, el compilador crea uno que realiza simplemente una copia bit a bit para que el constructor de copia de Composite lo pueda llamar. La llamada a Composite::print() en main() muestra que esto ocurre, porque el contenido de c2.wocc es idéntico al contenido de c.wocc. El proceso que realiza el compilador para crear un constructor de copia se denomina inicialización de miembros (memberwise initialization).

Se recomienda definir constructor de copia propio en vez de usar el que crea el compilador. Eso garantiza que estará bajo su control.