2014-09-08 27 views
5

Rozważmy następujący kod:czysto wirtualne wywołanie funkcji interesujących przypadków

#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; 
} 

w tym przypadku bezpośrednio wywołać funkcję wirtualnego z konstruktora i dostać ostrzeżenie kompilatora, który mówi:
ostrzegawczy: abstrakcyjne wirtualny „virtual void A: : f() "wywołany z konstruktora.
Ale to wykonuje bez wypowiedzenia i drukuje A.

Gdybym zawinąć wywołanie funkcji takiego:

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

Kompilator nie emituje żadnego ostrzeżenia podczas kompilacji ale miażdży przy starcie z następujących wiadomość:

pure virtual method called 
terminate called without active exception 
Abort 

Czy ktoś może wyjaśnić zachowanie obu tych przypadków?

+0

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

Odpowiedz

1

§ 10.4 Klasy abstrakcyjne [class.abstract]/P6

funkcje państwa mogą być wywoływane z konstruktora lub destruktora() z klasy abstrakcyjnej; efekt tworzenia wirtualnego połączenia (10.3) do czystego wirtualnego funkcję bezpośrednio lub pośredniodla obiektu powstaje (lub zniszczone) z taki konstruktor (lub destructor) jest zdefiniowana.

W skrócie: Efekt wywołania funkcji czysto wirtualnej bezpośrednio lub pośrednio dla obiektu tworzonego z konstruktora jest niezdefiniowany.

Wezwanie do czystych funkcji składowych wirtualnych nie może być używany z konstruktora lub destruktora, nie ważne czy połączenie jest bezpośrednie lub pośrednie, bo wtedy skończyć z zachowanie niezdefiniowane.

Jedynym użytecznym przykładem zapewniając realizację czystej funkcji wirtualnej jest gdy wywołanie go z klasy pochodnej:

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

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

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

W pierwszym przypadku kompilator zapisuje dane przez statyczną wysyłkę do A::f(), ponieważ zna statyczny typ A. Ale jest całkiem słuszne, że jest to strasznie nieokreślone zachowanie i nie powinieneś tego robić.

W drugim przypadku kompilator nie statycznie wysyła do A::f(), ponieważ wywołanie nie znajduje się w konstruktorze, więc musi dynamicznie go wysłać. Różne ABIs inaczej obsługują czyste połączenia wirtualne, ale zarówno MSVC, jak i Itanium mają dedykowaną czystą wirtualną procedurę obsługi połączeń, która jest umieszczana w vtable w celu wychwycenia tych zdarzeń. To właśnie tworzy komunikat o błędzie, który widzisz.

0

Undefined zachowanie oznacza, że ​​kompilator nie musi poradzić sobie z sytuacją w jakikolwiek szczególnie zdefiniowane sposób.

Tutaj twój kompilator, który znał rzeczywisty typ A w swoim konstruktorze, był w stanie wbudować czysto wirtualną metodę zamiast wywoływać ją przez v-table. Tak by się stało, gdyby metoda była normalna wirtualna, a nie czysto wirtualna, a to byłoby zdefiniowane zachowanie.

Chociaż byłoby to zachowanie również poprzez g(), kompilator nie zrobił tego dla czystej wirtualnej funkcji f(). To nie musi.

Prosta moralność nie wywołuje niezdefiniowanego zachowania, a jeśli chcesz wywołać f() z konstruktora, nie rób tego czysto wirtualnie.

Jeśli chcesz wymusić podklasy w celu implementacji f(), nie wywołuj ich od konstruktora A, ale nadaj tej funkcji, którą chcesz nazwać inną. Najlepiej nie wirtualny wcale.

1

Z punktu kompilator widzenia, jeśli spojrzeć na to, jak funkcja f() jest wywoływana:

  • Case-1: konstruktor A za połączenia A-konstruktor => f() bezpośrednio. Kompilator dokładnie wie, że tak jest i decyduje się wydać ostrzeżenie.
  • Case-2: A's ctor wywołuje A-ctor => g() => f(). Istnieją całkowicie uzasadnione przypadki wywoływania f() z jednej z metod klasy. Kompilator nie może powiedzieć, że jest to nielegalne. Callgraph mógł pochodzić z * => bar() => g() -> f(), co oznacza, że ​​typ obiektu nie jest znany. Takie ścieżki są możliwe, co wymaga dynamicznego rozsyłania - co prowadzi do błędu w czasie wykonywania.

Jak zauważyli inni, jest to niezdefiniowane użycie i kompilatory wykonują tylko tyle wykrywalności i ostrzegania.