2013-05-23 20 views
6

Sto leggendo Effective C++, e c'è il "Punto 9: Non chiamare mai le funzioni virtuali durante la costruzione o la distruzione". E mi chiedo se il mio codice è bene anche se si rompe questa regola:Chiamare una funzione virtuale dal costruttore

using namespace std; 

class A{ 
    public: 
     A(bool doLog){ 
      if(doLog) 
       log(); 
     } 

     virtual void log(){ 
      cout << "logging A\n"; 
     } 
}; 


class B: public A{ 
public: 
    B(bool doLog) : A(false){ 
     if(doLog) 
      log(); 
    } 

    virtual void log(){ 
     cout << "logging B\n"; 
    } 
}; 


int main() { 
    A a(true); 
    B b(true); 
} 

C'è qualcosa di sbagliato in questo approccio? Posso finire nei guai quando faccio qualcosa di più complicato?

cuciture a me che la maggior parte delle risposte non ha ottenuto quello che ho fatto lì, e hanno semplicemente spiegato ancora una volta il motivo per cui sta chiamando funzione virtuale dal costruttore potenzialmente pericolosi.

vorrei sottolineare che l'uscita del mio programma si presenta così:

logging A 
logging B 

Così ho un registrato quando si è costruito e B registrato quando si è costruito. E questo è quello che voglio ! Ma ti sto chiedendo se trovi qualcosa di sbagliato (potenzialmente pericoloso) con il mio "hack" per superare il problema con il richiamo della funzione virtuale nel costruttore.

+1

In questo caso l'hai corretto correttamente, ma se altre persone hanno bisogno di usare quel codice, gli errori sono tenuti a verificarsi. –

+2

@JoachimPileborg questo non è vero: il comportamento * è * definito. Durante la costruzione, le chiamate alle funzioni virtuali sono disabilitate (ad esempio viene utilizzata l'implementazione del tipo attualmente in costruzione). –

risposta

11

E mi chiedo se il codice miniera è bene anche se si rompe questa regola:

Dipende da cosa si intende per "fine". Il tuo programma è ben formato e il suo comportamento è ben definito, quindi non invocherà comportamenti indefiniti e cose del genere.

Tuttavia, quando si visualizza una chiamata a una funzione virtuale, è possibile che la chiamata venga risolta richiamando l'implementazione fornita dal tipo più derivato che sovrascrive quella funzione.

Tranne che durante la costruzione, il corrispondente oggetto secondario non è stato ancora costruito, quindi il suboject più derivato è quello attualmente in costruzione. Risultato: la chiamata viene inviata come se la funzione non fosse virtuale.

Questo è contro-intuitivo e il tuo programma non dovrebbe fare affidamento su questo comportamento. Pertanto, come programmatore esperto, dovresti abituarti a evitare un simile schema e seguire le linee guida di Scott Meyer.

+0

Ben spiegato, ma devo dire che lo trovo molto intuitivo. Ho programmato in C++ per anni e quando in seguito ho scoperto le lingue che chiamavano la versione di classi più derivate sono rimasto scioccato e inorridito. – Steve

15

C'è qualcosa di sbagliato in questo approccio?

risposta da Bjarne Stroustrup:

Posso chiamare una funzione virtuale da un costruttore?

Sì, ma attenzione. Potrebbe non fare quello che ti aspetti. In un costruttore, , il meccanismo di chiamata virtuale è disabilitato perché l'override delle classi derivate non è ancora avvenuta. Gli oggetti sono costruiti dalla base su, "base prima derivata". Si consideri:

#include<string> 
    #include<iostream> 
    using namespace std; 

class B { 
public: 
    B(const string& ss) { cout << "B constructor\n"; f(ss); } 
    virtual void f(const string&) { cout << "B::f\n";} 
}; 

class D : public B { 
public: 
    D(const string & ss) :B(ss) { cout << "D constructor\n";} 
    void f(const string& ss) { cout << "D::f\n"; s = ss; } 
private: 
    string s; 
}; 

int main() 
{ 
    D d("Hello"); 
} 

il programma viene compilato e produrre

B constructor 
B::f 
D constructor 

Nota Non D :: f. Considerare cosa accadrebbe se la regola fosse diversa in modo che D :: f() fosse chiamato da B :: B(): poiché il costruttore D :: D() non era ancora stato eseguito, D :: f() sarebbe prova ad assegnare il suo argomento a una stringa non inizializzata s. Il risultato sarebbe molto probabilmente un incidente immediato. La distruzione viene eseguita "classe derivata prima della classe base", quindi le funzioni virtuali si comportano come nei costruttori: vengono utilizzate solo le definizioni locali e non vengono effettuate chiamate alle funzioni di override per evitare di toccare la parte della classe derivata (ora distrutta) dell'oggetto.

Per maggiori dettagli si veda D & E 13.2.4.2 o TC++ PL3 15.4.3.

E 'stato suggerito che questa regola è un artefatto implementazione. Non è così. In effetti, sarebbe notevolmente più semplice implementare la regola non sicura di chiamare le funzioni virtuali dai costruttori esattamente come da altre funzioni. Tuttavia, ciò implicherebbe che nessuna funzione virtuale potrebbe essere scritta per fare affidamento su invarianti stabiliti dalle classi base. Sarebbe un disastro terribile.

4

È "bello" nel senso di essere ben definito. Potrebbe non essere "buono" nel senso di fare ciò che ti aspetti.

Chiamerai l'override della classe attualmente in costruzione (o distrutta), non l'override finale; poiché la classe derivata finale non è stata ancora costruita (o è già stata distrutta) e quindi non è possibile accedervi. Quindi potresti metterti nei guai se vuoi che l'override finale venga chiamato qui.

Poiché questo comportamento è potenzialmente confuso, è meglio evitare di doverlo fare. Raccomanderei di aggiungere un comportamento ad una classe tramite aggregazione piuttosto che la sottoclasse in quella situazione; i membri della classe sono costruiti prima del corpo del costruttore e durano fino a dopo il distruttore, quindi sono disponibili in entrambi i luoghi.

Una cosa che non devi fare è chiamare una funzione virtuale dal costruttore o dal distruttore se è pura virtuale in quella classe; è un comportamento indefinito.

Problemi correlati