sencillos polimorfismo herencia ejemplos c++ constructor abstract-class object-lifetime pure-virtual

herencia - ejemplos sencillos de polimorfismo en c++



llamar a la función virtual pura desde el constructor de la clase base (5)

Tengo una clase base MyBase que contiene una función virtual pura:

void PrintStartMessage() = 0

Quiero que cada clase derivada lo llame en su constructor

luego lo puse en el constructor de la clase base ( MyBase )

class MyBase { public: virtual void PrintStartMessage() =0; MyBase() { PrintStartMessage(); } }; class Derived:public MyBase { public: void PrintStartMessage(){ } }; void main() { Derived derived; }

pero obtengo un error de enlazador.

this is error message : 1>------ Build started: Project: s1, Configuration: Debug Win32 ------ 1>Compiling... 1>s1.cpp 1>Linking... 1>s1.obj : error LNK2019: unresolved external symbol "public: virtual void __thiscall MyBase::PrintStartMessage(void)" (?PrintStartMessage@MyBase@@UAEXXZ) referenced in function "public: __thiscall MyBase::MyBase(void)" (??0MyBase@@QAE@XZ) 1>C:/Users/Shmuelian/Documents/Visual Studio 2008/Projects/s1/Debug/s1.exe : fatal error LNK1120: 1 unresolved externals 1>s1 - 2 error(s), 0 warning(s)

Quiero forzar a todas las clases derivadas a ...

A- implement it B- call it in their constructor

¿Cómo debo hacerlo?


Hay muchos artículos que explican por qué nunca debe llamar funciones virtuales en constructor y destructor en C ++. Mire here y here para obtener más información sobre lo que ocurre detrás de la escena durante esas llamadas.

En resumen, los objetos se construyen desde la base hasta la derivada. Por lo tanto, cuando intenta llamar a una función virtual desde el constructor de la clase base, la anulación de las clases derivadas aún no se ha producido debido a que los constructores derivados aún no han sido llamados.


Lo más cerca que puede llegar a hacer algo así es construir completamente su objeto primero y luego llamar al método después:

template <typename T> T construct_and_print() { T obj; obj.PrintStartMessage(); return obj; } int main() { Derived derived = construct_and_print<Derived>(); }

Tratar de llamar a un método abstracto puro desde un derivado mientras ese objeto aún se está construyendo es inseguro. Es como tratar de llenar de gasolina un automóvil, pero ese automóvil todavía está en la línea de ensamblaje y aún no se ha instalado el tanque de gasolina. Quiero decir, ¿qué diablos esperas que pase?


No debe llamar a una función virtual en un constructor. Period Tendrá que encontrar alguna solución, como hacer que PrintStartMessage no sea virtual y poner la llamada explícitamente en cada constructor.


No puede hacerlo de la forma que imagina porque no puede llamar a funciones virtuales derivadas desde el constructor de la clase base; el objeto aún no es del tipo derivado. Pero no necesitas hacer esto.

Llamar a PrintStartMessage después de la construcción de MyBase

Supongamos que quieres hacer algo como esto:

class MyBase { public: virtual void PrintStartMessage() = 0; MyBase() { printf("Doing MyBase initialization.../n"); PrintStartMessage(); // ⚠ UB: pure virtual function call ⚠ } }; class Derived : public MyBase { public: virtual void PrintStartMessage() { printf("Starting Derived!!!/n"); } };

El rastro de ejecución deseado sería:

Doing MyBase initialization... Starting Derived!!!

¡Pero esto es para lo que son los constructores! ¡Deseche la función virtual y haga que el constructor del derivado haga el trabajo!

class MyBase { public: MyBase() { printf("Doing MyBase initialization.../n"); } }; class Derived : public MyBase { public: Derived() { printf("Starting Derived!!!/n"); } };

La salida es, bueno, lo que esperaríamos:

Doing MyBase initialization... Starting Derived!!!

Sin embargo, esto no aplica las clases derivadas para implementar explícitamente la funcionalidad PrintStartMessage . Pero, por otro lado, piense dos veces si es necesario, ya que de lo contrario siempre pueden proporcionar una implementación vacía de todos modos.

Llamar a PrintStartMessage antes de la construcción de MyBase

Como se dijo anteriormente, si va a llamar a PrintStartMessage antes de que Derived haya sido un constructor, no podrá lograr esto porque aún no hay un objeto Derived para PrintStartMessage . No tendría sentido exigir que PrintStartMessage sea ​​un miembro no estático porque no tendría acceso a ninguno de los miembros de datos Derived .

Una función estática con función de fábrica

Alternativamente podemos hacer que sea un miembro estático como ese:

class MyBase { public: MyBase() { printf("Doing MyBase initialization.../n"); } }; class Derived : public MyBase { public: static void PrintStartMessage() { printf("Derived specific message./n"); } };

Una pregunta natural surge de cómo se llamará?

Hay dos soluciones que puedo ver: una es similar a la de @greatwolf, donde tienes que llamarla manualmente. Pero ahora, dado que es un miembro estático, puede llamarlo antes de que se haya construido una instancia de MyBase :

template<class T> T print_and_construct() { T::PrintStartMessage(); return T(); } int main() { Derived derived = print_and_construct<Derived>(); }

La salida será

Derived specific message. Doing MyBase initialization...

Este enfoque obliga a todas las clases derivadas a implementar PrintStartMessage . Lamentablemente, solo es cierto cuando los construimos con nuestra función de fábrica ... que es una gran desventaja de esta solución.

La segunda solución es recurrir al patrón de plantilla curiosamente recurrente (CRTP). Al decirle a MyBase el tipo de objeto completo en tiempo de compilación, puede hacer la llamada desde el constructor:

template<class T> class MyBase { public: MyBase() { T::PrintStartMessage(); printf("Doing MyBase initialization.../n"); } }; class Derived : public MyBase<Derived> { public: static void PrintStartMessage() { printf("Derived specific message./n"); } };

El resultado es el esperado, sin la necesidad de utilizar una función de fábrica dedicada.

Accediendo a MyBase desde PrintStartMessage con CRTP

Cuando MyBase está ejecutando, ya está bien para acceder a sus miembros. Podemos hacer que PrintStartMessage pueda acceder al MyBase que lo ha llamado:

template<class T> class MyBase { public: MyBase() { T::PrintStartMessage(this); printf("Doing MyBase initialization.../n"); } }; class Derived : public MyBase<Derived> { public: static void PrintStartMessage(MyBase<Derived> *p) { // We can access p here printf("Derived specific message./n"); } };

Lo siguiente también es válido y se usa con mucha frecuencia, aunque es un poco peligroso:

template<class T> class MyBase { public: MyBase() { static_cast<T*>(this)->PrintStartMessage(); printf("Doing MyBase initialization.../n"); } }; class Derived : public MyBase<Derived> { public: void PrintStartMessage() { // We can access *this member functions here, but only those from MyBase // or those of Derived who follow this same restriction. I.e. no // Derived data members access as they have not yet been constructed. printf("Derived specific message./n"); } };

Sin plantillas, solución, rediseño

Otra opción más es rediseñar un poco tu código. IMO esta es en realidad la solución preferida si tiene que llamar a un PrintStartMessage anulado desde la construcción de MyBase .

Esta propuesta consiste en separar Derived from MyBase , de la siguiente manera:

class ICanPrintStartMessage { public: virtual ~ICanPrintStartMessage() {} virtual void PrintStartMessage() = 0; }; class MyBase { public: MyBase(ICanPrintStartMessage *p) : _p(p) { _p->PrintStartMessage(); printf("Doing MyBase initialization.../n"); } ICanPrintStartMessage *_p; }; class Derived : public ICanPrintStartMessage { public: virtual void PrintStartMessage() { printf("Starting Derived!!!/n"); } };

Inicializas MyBase siguiente manera:

int main() { Derived d; MyBase b(&d); }


Si PrintStartMessage () no era una función virtual pura sino una función virtual normal, el compilador no se quejaría de ello. Sin embargo, todavía tendría que averiguar por qué no se está llamando a la versión derivada de PrintStartMessage ().

Como la clase derivada llama al constructor de la clase base antes que su propio constructor, la clase derivada se comporta como la clase base y, por lo tanto, llama a la función de la clase base.