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.