chiama alla funzione virtuale pura dal costruttore della class base

Ho una class base MyBase che contiene una pura funzione virtuale:

void PrintStartMessage() = 0

Voglio che ogni class derivata la chiami nel loro costruttore

quindi l’ho inserito nel costruttore della class base ( MyBase )

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

ma ottengo un errore linker.

      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)" ([email protected]@@UAEXXZ) referenced in function "public: __thiscall MyBase::MyBase(void)" ([email protected]@[email protected]) 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) 

    Voglio forza per tutte le classi derivate a …

     A- implement it B- call it in their constructor 

    Come devo farlo?

    Ci sono molti articoli che spiegano perché non dovresti mai chiamare funzioni virtuali in costruttore e distruttore in C ++. Dai un’occhiata qui e qui per i dettagli su cosa succede dietro la scena durante tali chiamate.

    In breve, gli oggetti sono costruiti dalla base fino alla derivata. Pertanto, quando si tenta di chiamare una funzione virtuale dal costruttore della class base, l’override delle classi derivate non si è ancora verificato perché i costruttori derivati ​​non sono stati ancora chiamati.

    La cosa più vicina a cui puoi fare qualcosa del genere è build completamente il tuo object prima e poi chiamare il metodo dopo:

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

    Cercare di chiamare un metodo astratto puro da una derivata mentre quell’object è ancora in costruzione non è sicuro. È come cercare di riempire il gas in un’automobile ma quella macchina è ancora sulla catena di assembly e il serbatoio del gas non è stato ancora inserito. Voglio dire cosa diavolo ti aspetti che accada?

    Non puoi farlo come immagini perché non puoi chiamare funzioni virtuali derivate dal costruttore della class base: l’object non è ancora del tipo derivato. Ma non hai bisogno di farlo.

    Chiamare PrintStartMessage dopo la costruzione di MyBase

    Supponiamo che tu voglia fare qualcosa del genere:

     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"); } }; 

    La traccia di esecuzione desiderata sarebbe:

     Doing MyBase initialization... Starting Derived!!! 

    Ma questo è ciò che i costruttori sono per! Basta eliminare la funzione virtuale e rendere il costruttore del derivato per fare il lavoro!

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

    L’output è, beh, quello che ci aspetteremmo:

     Doing MyBase initialization... Starting Derived!!! 

    Ciò non impone tuttavia alle classi derivate di implementare esplicitamente la funzionalità PrintStartMessage . D’altra parte, pensateci due volte se è necessario, poiché altrimenti possono comunque fornire un’implementazione vuota.

    Chiamare PrintStartMessage prima della costruzione di MyBase

    Come detto sopra, se si deve chiamare PrintStartMessage prima che Derived sia stato costruito, non è ansible farlo perché non è ancora presente un object Derived per l’ PrintStartMessage di PrintStartMessage . Non avrebbe senso richiedere a PrintStartMessage di essere un membro non statico perché non avrebbe accesso a nessuno dei membri dei dati Derived .

    Una funzione statica con funzione di fabbrica

    In alternativa possiamo renderlo membro statico in questo modo:

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

    Una domanda naturale sorge da come sarà chiamata?

    Ci sono due soluzioni che posso vedere: una è simile a quella di @greatwolf, dove devi chiamarla manualmente. Ma ora, dato che è un membro statico, puoi chiamarlo prima che sia stata MyBase un’istanza di MyBase :

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

    L’output sarà

     Derived specific message. Doing MyBase initialization... 

    Questo approccio forza tutte le classi derivate a implementare PrintStartMessage . Sfortunatamente è vero solo quando li costruiamo con la nostra funzione di fabbrica … che è un enorme svantaggio di questa soluzione.

    La seconda soluzione è quella di ricorrere al modello di modello curiosamente ricorrente (CRTP). MyBase a MyBase il tipo di object completo al momento della compilazione, può fare la chiamata dal costruttore:

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

    L’output è come previsto, senza la necessità di utilizzare una funzione di fabbrica dedicata.

    Accesso a MyBase da PrintStartMessage con CRTP

    Quando MyBase è in esecuzione, è già OK per accedere ai suoi membri. Possiamo rendere PrintStartMessage in grado di accedere a MyBase che l’ha chiamato:

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

    Il seguente è anche valido e molto usato, anche se un po ‘pericoloso:

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

    Nessuna riprogettazione della soluzione dei modelli

    Un’altra opzione è ridisegnare il codice un po ‘. IMO questo è in realtà la soluzione preferita se si deve assolutamente chiamare un PrintStartMessage override dalla costruzione di MyBase .

    Questa proposta deve separare Derived from MyBase , come segue:

     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"); } }; 

    Inizializza MyBase come segue:

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

    Non dovresti chiamare una funzione virtual in un costruttore. Periodo Dovrai trovare qualche soluzione, come rendere PrintStartMessage non- virtual e mettere esplicitamente la chiamata in ogni costruttore.

    Se PrintStartMessage () non era una funzione virtuale pura ma una normale funzione virtuale, il compilatore non si lamentava di ciò. Tuttavia, dovresti ancora capire perché la versione derivata di PrintStartMessage () non viene chiamata.

    Poiché la class derivata chiama il costruttore della class base prima del proprio costruttore, la class derivata si comporta come la class base e quindi chiama la funzione della class base.