Si permite que el compilador sintetice un constructor de copia para una clase derivada, éste llamará automáticamente al constructor de copia de la clase base y después a los constructores de copia de todos los objetos miembro (o realizará una copia de bits en los tipos predefinidos), de modo que obtendrá el comportamiento correcto:
//: C14:CopyConstructor.cpp // Correctly creating the copy-constructor #include <iostream> using namespace std; class Parent { int i; public: Parent(int ii) : i(ii) { cout << "Parent(int ii)\n"; } Parent(const Parent& b) : i(b.i) { cout << "Parent(const Parent&)\n"; } Parent() : i(0) { cout << "Parent()\n"; } friend ostream& operator<<(ostream& os, const Parent& b) { return os << "Parent: " << b.i << endl; } }; class Member { int i; public: Member(int ii) : i(ii) { cout << "Member(int ii)\n"; } Member(const Member& m) : i(m.i) { cout << "Member(const Member&)\n"; } friend ostream& operator<<(ostream& os, const Member& m) { return os << "Member: " << m.i << endl; } }; class Child : public Parent { int i; Member m; public: Child(int ii) : Parent(ii), i(ii), m(ii) { cout << "Child(int ii)\n"; } friend ostream& operator<<(ostream& os, const Child& c){ return os << (Parent&)c << c.m << "Child: " << c.i << endl; } }; int main() { Child c(2); cout << "calling copy-constructor: " << endl; Child c2 = c; // Calls copy-constructor cout << "values in c2:\n" << c2; } ///:~
Listado 14.18. C14/CopyConstructor.cpp
El operator<< de Child es interesante por la forma en que llama al operator<< de la parte Parent que contiene: moldeando el objeto Child a Parent& (si lo moldea a un objeto de la clase base en lugar de a una referencia, normalmente obtendrá resultados no deseados):
return os << (Parent&)c << c.m
Como el compilador lo ve entonces como un Parent, llama a la versión de operator<< de Parent.
Puede observar que Child no tiene un constructor de copia definido explícitamente. El compilador sintetiza entonces el constructor de copia (ya que es una de las cuatro funciones que sintetiza, junto con el constructor por defecto, si no crea ningún constructor, el operator= y el destructor) llamando al constructor de copia de Parent y al constructor de copia de Member. Así se muestra en la salida:
Parent(int ii) Member(int ii) Child(int ii) calling copy-constructor: Parent(const Parent&) Member(const Member&) values in c2: Parent: 2 Member: 2 Child: 2
Sin embargo, si intenta escribir su propio constructor de copia para Child, comete un error inocente y lo hace mal:
Child(const Child& c) : i(c.i), m(c.m) {}
entonces se llamará automáticamente al constructor por defecto para la parte de la clase base de Child, ya que es a lo que recurre el compilador cuando no tiene otro constructor al que llamar (recuerde que siempre se debe llamar a algún constructor para cada objeto, sea o no un subobjeto de otra clase). La salida será entonces:
Parent(int ii) Member(int ii) Child(int ii) calling copy-constructor: Parent() Member(const Member&) values in c2: Parent: 0 Member: 2 Child: 2
Probablemente esto no es lo que espera, ya que generalmente deseará que la parte de la clase base se copie del objeto existente al nuevo objeto como parte de la construcción por copia.
Para arreglar el problema, debe acordarse de llamar correctamente al constructor de copia de la clase base (como hace el compilador) siempre que escriba su propio constructor de copia. Puede parecer un poco extraño a primera vista, pero es otro ejemplo de upcasting:
Child(const Child& c) : Parent(c), i(c.i), m(c.m) { cout << "Child(Child&)\n"; }
La parte extraña es dónde se llama al constructor de copia de Parent: Parent(c). ¿Qué significa pasar un objeto Child a un constructor de Parent? Pero Child hereda de Parent, así que una referencia a Child es una referencia a Parent. La llamada al constructor de copia de la clase base hace un upcast de una referencia a Child a una referencia a Parent y la utiliza para realizar la construcción por copia. Cuando escriba sus propios constructores de copia, casi siempre querrá hacer lo mismo.