2014-09-08 10 views
5

Si consideri il seguente codice:Pure casi interessanti chiamata di funzione virtual

#include <iostream> 
using namespace std; 

class A 
{ 
    public: 
    virtual void f() = 0; 
    A(){f();} 
}; 

void A::f() { 
    cout<<"A"<<endl; 
} 

class B:public A{ 
public: 
    void f(){cout<<"B"<<endl;} 
}; 
int main() 
{ 
B b; 
} 

In questo caso io chiamo direttamente la funzione virtuale dal costruttore e ottenere avviso del compilatore che dice:
avvertimento: astratto virtuale 'vuoto A virtuali: : f() 'chiamato dal costruttore.
Ma esegue senza interruzione e stampe A.

Se mi avvolgo il richiamo della funzione come questa:

class A 
{ 
    public: 
    virtual void f() = 0; 
    A(){g();} 
    void g(){f();} 
}; 

void A::f(){cout<<"A"<<endl;} 

class B:public A{ 
public: 
    void f(){cout<<"B"<<endl;} 
}; 
int main() 
{ 
B b; 
} 

Il compilatore non emette alcun avviso durante la compilazione ma schiaccia in fase di esecuzione con il seguente messaggio:

pure virtual method called 
terminate called without active exception 
Abort 

Qualcuno può spiegare il comportamento di entrambi i casi?

+0

Suggerimento: http://stackoverflow.com/questions/962132/calling-virtual-functions-inside-constructors – dragosht

risposta

1

§ 10.4 Le classi astratte [class.abstract]/P6

funzioni membro può essere chiamato da un costruttore (o distruttore) di una classe astratta; l'effetto di rendere una chiamata virtuale (10.3) per pura funzione virtuale direttamente o indirettamente per l'oggetto viene creato (o distrutto) da tale costruttore (o distruttore) è indefinito.

In breve: L'effetto di fare una chiamata a una funzione virtuale pura direttamente o indirettamente per l'oggetto creato dal costruttore è indefinito.

Una chiamata a funzioni membro virtuali pure non può essere utilizzato da un costruttore o un distruttore, non importa se la chiamata è diretta o indiretta, perché poi si finisce con un comportamento non definito .

L'unico esempio utile di prevedere l'attuazione di una funzione virtuale pura è quando si chiama da una classe derivata:

struct A 
{ 
    virtual void f() = 0; 
}; 

void A::f() 
{ 
    cout<<"A"<<endl; 
} 

struct B : A 
{ 
    void f() 
    { 
     A::f(); 
     cout<<"B"<<endl; 
    } 
}; 
1

Nel primo caso, il compilatore capita di salvarvi inviando staticamente a A::f(), poiché conosce il tipo statico di A. Ma è giusto che questo comportamento sia orribilmente indefinito e non dovresti farlo.

Nel secondo caso, il compilatore non viene inviato staticamente a A::f() poiché la chiamata non è nel costruttore, quindi deve inviarlo in modo dinamico. I vari ABI gestiscono le chiamate virtuali pure in modo diverso, ma sia MSVC che Itanium hanno un gestore di chiamate virtuali dedicato dedicato che viene collocato nel vtable per catturare questi eventi. Questo è ciò che produce il messaggio di errore che vedi.

0

Undefined comportamento significa che il compilatore non deve gestire la situazione in qualsiasi particolare definita maniera.

Qui il tuo compilatore, che conosceva il tipo effettivo di A nel suo costruttore, era in grado di eseguire l'inline nel metodo virtuale puro piuttosto che chiamarlo tramite la v-table. Questo è ciò che accadrebbe se il metodo fosse normale virtuale, non puro virtuale, e questo sarebbe un comportamento definito.

Anche se sarebbe il comportamento anche tramite g(), il compilatore non ha eseguito questa operazione per una pura funzione virtuale f(). Non è necessario.

La morale semplice è non invocare un comportamento non definito, e se si desidera chiamare f() dal costruttore non renderlo puro virtuale.

Se si desidera imporre alle sottoclassi l'implementazione di f(), non chiamarlo dal costruttore di A, ma assegnare a tale funzione un nome diverso. Preferibilmente non virtuale affatto.

1

Dal punto di vista di un compilatore, se si guarda a come viene richiamata la funzione f():

  • Case-1: chiama ctor di un A-ctor => f() direttamente. Il compilatore sa esattamente che questo è il caso e decide di emettere un avvertimento.
  • Caso 2: il chiamante A chiama A-ctor => g() => f(). Ci sono casi completamente legittimi di chiamare f() da uno dei metodi di classe. Il compilatore non può dire che questo è illegale. Il callgraph potrebbe essere stato da * => bar() => g() -> f(), il che significa che il tipo di oggetto non è noto. Poiché tali percorsi sono possibili, è necessario un dispatching dinamico, che porta all'errore di runtime.

Come altri hanno sottolineato, si tratta di un utilizzo non definito e i compilatori arrivano solo a rilevare e avvertire.