No todas las funciones son heredadas automáticamente desde la clase base a la clase derivada. Los constructores y destructores manejan la creación y la destrucción de un objeto y sólo ellos saben que hacer con los aspectos de un objeto en sus clases particulares y por ello los constructores y destructores inferiores de la jerarquía deben llamarlos. Así, los constructores y destructores no se heredan y deben ser creados específicamente en cada clase derivada.
Además, operator= tampoco se hereda porque realiza una acción parecida al constructor. Esto es, sólo porque conoce como asignar todos los miembros de un objeto, la parte izquierda del = a la parte derecha del otro objeto, no significa que la asignación tendrá el mismo significado después de la herencia.
En la herencia, estas funciones son creadas por el compilador si no son creadas por usted. (Con constructores, no se pueden crear constructores para que el compilador cree el constructor por defecto y el constructor copia.) Esto fue brevemente descrito en el capítulo 6. Los constructores creados se usan en inicialización de sus miembros y la creación del operator= usa la asignación de los miembros. A continuación, un ejemplo de las funciones que son creadas por el compilador.
//: C14:SynthesizedFunctions.cpp // Functions that are synthesized by the compiler #include <iostream> using namespace std; class GameBoard { public: GameBoard() { cout << "GameBoard()\n"; } GameBoard(const GameBoard&) { cout << "GameBoard(const GameBoard&)\n"; } GameBoard& operator=(const GameBoard&) { cout << "GameBoard::operator=()\n"; return *this; } ~GameBoard() { cout << "~GameBoard()\n"; } }; class Game { GameBoard gb; // Composition public: // Default GameBoard constructor called: Game() { cout << "Game()\n"; } // You must explicitly call the GameBoard // copy-constructor or the default constructor // is automatically called instead: Game(const Game& g) : gb(g.gb) { cout << "Game(const Game&)\n"; } Game(int) { cout << "Game(int)\n"; } Game& operator=(const Game& g) { // You must explicitly call the GameBoard // assignment operator or no assignment at // all happens for gb! gb = g.gb; cout << "Game::operator=()\n"; return *this; } class Other {}; // Nested class // Automatic type conversion: operator Other() const { cout << "Game::operator Other()\n"; return Other(); } ~Game() { cout << "~Game()\n"; } }; class Chess : public Game {}; void f(Game::Other) {} class Checkers : public Game { public: // Default base-class constructor called: Checkers() { cout << "Checkers()\n"; } // You must explicitly call the base-class // copy constructor or the default constructor // will be automatically called instead: Checkers(const Checkers& c) : Game(c) { cout << "Checkers(const Checkers& c)\n"; } Checkers& operator=(const Checkers& c) { // You must explicitly call the base-class // version of operator=() or no base-class // assignment will happen: Game::operator=(c); cout << "Checkers::operator=()\n"; return *this; } }; int main() { Chess d1; // Default constructor Chess d2(d1); // Copy-constructor //! Chess d3(1); // Error: no int constructor d1 = d2; // Operator= synthesized f(d1); // Type-conversion IS inherited Game::Other go; //! d1 = go; // Operator= not synthesized // for differing types Checkers c1, c2(c1); c1 = c2; } ///:~
Listado 14.10. C14/SynthesizedFunctions.cpp
Los constructores y el operator= de GameBoard y Game se describen por si solos y por ello distinguirá cuando son utilizados por el compilador. Además, el operador Other() ejecuta una conversión automática de tipo desde un objeto Game a un objeto de la clase anidada Other. La clase Chess simplemente hereda de Game y no crea ninguna función (sólo para ver como responde el compilador) La función f() coge un objeto Other para comprobar la conversión automática del tipo.
En main(), el constructor creado por defecto y el constructor copia de la clase derivada Chess son ejecutados. Las versiones de Game de estos constructores son llamados como parte de la jerarquía de llamadas a los constructores. Aun cuando esto es parecido a la herencia, los nuevos constructores son realmente creados por el compilador. Como es de esperar, ningún constructor con argumentos es ejecutado automáticamente porque es demasiado trabajo para el compilador y no es capaz de intuirlo.
El operator= es también es creado como una nueva función en Chess usando la asignación (así, la versión de la clase base es ejecutada) porque esta función no fue explícitamente escrita en la nueva clase. Y, por supuesto el destructor es creado automáticamente por el compilador.
El porqué de todas estas reglas acerca de la reescritura de funciones en relación a la creación de un objeto pueden parecer un poco extrañas en una primera impresión y como se heredan las conversiones automáticas de tipo. Pero todo esto tiene sentido - si existen suficiente piezas en Game para realizar un objeto Other, aquellas piezas están todavía en cualquier objeto derivado de Game y el tipo de conversión es válido (aun cuando puede, si lo desea, redefinirlo).
El operator= es creado automáticamente sólo para asignar objeto del mismo tipo. Si desea asignar otro tipo, deberá escribir el operator= usted mismo.
Si mira con detenimiento Game, observará que el constructor copia y la asignación tienen llamadas explicitas a constructor copia del objeto miembro y al operador de asignación. En la mayoría de ocasiones, deberá hacer esto porque sino, en vez del constructor copia, será llamado el constructor por defecto del objeto miembro, y en el caso del operador de asignación, ¡ninguna asignación se hará en los objetos miembros!
Por último, fíjese en Checkers, dónde explícitamente se escribe un constructor por defecto, constructor copia y los operadores de asignación. En el caso del constructor por defecto, el constructor por defecto de la clase base se llama automáticamente, y esto es lo normalmente que se desea hacer. Pero, aquí existe un punto importante, tan pronto que se decide escribir nuestro propio constructor copia y operador de asignación, el compilador asume que usted sabe lo que esta haciendo y no ejecutará automáticamente las versiones de la clase base así como las funciones creadas automáticamente. Si se quiere ejecutar las versiones de la clase base, debe llamarlas explícitamente. En el constructor copia de Checkers, esta llamada aparece en la lista de inicialización del constructor:
Checkers(const Checkers& c) : Game(c) {
En el operador de asignación de Checkers, la clase base se llama en la primera línea del cuerpo de la función:
Game::operator=(c);
Estas llamadas deben seguirse de forma canónica cuando usa cualquier clase derivada.
Las funciones miembro estáticas funcionan igual que las funciones miembros no estáticas:
Son heredadas en la clase derivada.
Si redefine un miembro estático, el resto de funciones sobrecargadas en la clase base son ocultas.
Si cambia la signatura de una función en la clase base, todas las versiones con ese nombre de función en la clase base son ocultadas (esto es realmente una variación del punto anterior).
Sin embargo, las funciones miembro estáticas no pueden ser virtuales (este tema se cubrirá detenidamente en el capítulo 15).