2009-06-30 10 views
16

Próbowałem dowiedzieć się, co się dzieje, gdy klasa pochodna deklaruje funkcję wirtualną jako prywatną. Poniżej znajduje się program, który napisałemPubliczna funkcja wirtualna pochodna prywatna w C++

#include <iostream> 
using namespace std; 
class A 
{ 
    public: 
     virtual void func() { 
     cout<<"A::func called"<<endl; 
    } 
    private: 
}; 
class B:public A 
{ 
    public: 
    B() 
    { 
     cout<<"B constructor called"<<endl; 
    } 
    private: 
    void func() { 
     cout<<"B::func called"<<endl; 
    } 
}; 
int main() 
{ 
    A *a = new B(); 
    a->func(); 
    return 0; 
} 

Zaskakująco (dla mnie) było wyjście:

B constructor called 
B::func called 

nie jest to naruszenie prywatny zestaw dostęp do tej funkcji. Czy to oczekiwane zachowanie? Czy jest to standardowe obejście lub luka? Czy poziomy dostępu są pomijane podczas rozwiązywania wywołań funkcji przez VTABLE?

Każdy wgląd w to zachowanie byłby bardzo pomocny.

Ponadto wspomniano, że prywatne przejęcie wirtualnego członka uniemożliwiłoby dalsze klasy dziedziczenie go. Nawet to ma problemy. Modyfikacja powyższego programu obejmują:

class C: public B 
{ 
    public: 
    void func() { 
     cout<<"C::func called"<<endl; 
    } 
}; 

i główny program testowy do:

int main() 
{ 
    A *a = new C(); 
    a->func(); 
    return 0; 
} 

wyjście jest:

C::func called 
+1

Chciałem tylko wskazać, że jeden * nie może * zrobić tego w Javie. Gdy metoda jest nadpisywana, musi mieć poziom dostępu równy lub bardziej publiczny niż metoda, która jest nadpisywana. Powodem jest to, że musisz upewnić się, że wersja metody klasy podklasy może być wywołana ze wszystkich kontekstów, z których można wywołać wersję nadklasy. Uważam, że jest to interesujące w C++. – Tom

+0

No cóż, mówią, że C++ pozwala ci się powiesić, jeśli się na to pochylisz. – Naveen

Odpowiedz

10

Zachowanie jest prawidłowe. Za każdym razem, gdy deklarujesz swoją funkcję jako "wirtualną", instruujesz kompilator do generowania połączenia wirtualnego zamiast bezpośredniego wywoływania tej funkcji. Za każdym razem, gdy zastępujesz funkcję wirtualną w klasie potomków, określasz zachowanie tej funkcji (nie zmieniasz trybu dostępu dla tych klientów, którzy polegają na interfejsie "rodzica").

Zmiana trybu dostępu dla funkcji wirtualnej w klasie potomków oznacza, że ​​chcesz ją ukryć przed tymi klientami, którzy bezpośrednio korzystają z klasy potomka (którzy polegają na interfejsie "potomnym").

Rozważmy przykład:

void process(const A* object) { 
    object->func(); 
} 

funkcja "proces" opiera się na interfejsie rodzica. Oczekuje się, że będzie działać dla każdej klasy, publicznie pochodzącej z A. Nie możesz publicznie wywodzić B z A (mówiąc "każdy B jest A"), ale ukryć część jego interfejsu. Ci, którzy oczekują "A", muszą otrzymać w pełni funkcjonalny "A".

+0

Zgadzam się. Tylko, że kompilator konwertujący funkcję Prywatną na publiczną wydawał się nie do przyjęcia.Ale prawda "Nie możesz publicznie wywodzić B od A (mówiąc" każdy B jest A "), ale ukryć część jego interfejsu. Ci, którzy oczekują" A ", muszą otrzymać w pełni funkcjonalny" A "." Dzięki za wyjaśnienie. –

+0

Dzięki, SadSido. Wygląda na to, że jesteś jedyną osobą, która mówi o "publicznym pochodzeniu". Zapomniałem, że istnieje coś więcej niż dziedziczenie publiczne, ponieważ korzystam tylko z publicznego dziedzictwa. Charakter tego publicznego dziedzictwa pozwala na takie zachowanie - co ma więcej sensu. Myślałem, że to najlepsze wytłumaczenie. Dziękuję Ci. – Tom

+0

Dziękuję wszystkim. Jest jednak jedna kwestia w tej kwestii, która mnie niepokoi. Mogę mieć wskaźnik do obiektu Child i wywołać jego prywatną metodę po prostu przez static_casting to do obiektu Parent ... Cóż, myślę, że static_cast w tym przypadku oznacza "polegać na interfejsie Parent" ... – SadSido

11

ta jest dobrze zdefiniowane zachowanie. Jeśli a byłby B*, to nie skompilowałoby się. Powodem jest to, że dostęp do elementu jest statycznie rozwiązywany przez kompilator, a nie dynamicznie w czasie wykonywania. Wiele książek w C++ sugeruje, że unikasz takiego kodowania, ponieważ dezorientuje mniej doświadczonych programistów.

+0

Co masz na myśli, że dostęp do elementu jest statycznie rozwiązywany przez kompilator? Cały punkt tabeli vtable polega na tym, że kompilator nie wie, którą funkcję wywołać w czasie kompilacji ... więc musi to sprawdzić w czasie wykonywania. Możesz wyjaśnić? – Tom

+3

@ Tom: W czasie kompilacji zaznaczona jest semantyka twojego programu. Wywołujesz func() używając A *, które jest poprawnym połączeniem. Comipler nie ma możliwości dowiedzenia się, że to wywołanie przechodzi do wywołania funkcji prywatnej członka w czasie wykonywania. W czasie wykonywania nie ma koncepcji prywatnej ani publicznej, jest to po prostu wywołanie funkcji. Jeśli używałeś B * zamiast A *, wówczas wywołanie func() byłoby nieprawidłowe, a kompilator oznaczałby to jako błąd. – Naveen

+0

@Naveen: Dziękuję. Przypuszczam, że zapomniałem, że kompilator nie zachował informacji o dostępie w czasie wykonywania ... dla mnie, to wydaje się niepożądanym zachowaniem. Zwłaszcza, że ​​możesz po prostu rzucić B do A, aby spróbować uzyskać dostęp do prywatnej funkcji. Teraz to ma sens ... Po prostu mi się to nie podoba :-). – Tom

3

Cóż, dzwonisz pod numer A::func(), który jest public, ale w obiekcie B jest on nadpisywany przez B::func(). Jest to wspólny wzór z następującymi konsekwencjami:

  • func nie jest przeznaczony do wezwał pochodzących B obiektów

  • func nie może być zmieniona w klasach pochodnych od B

+0

Jeśli powiesz "func nie jest przeznaczony do wywoływania w pochodnych obiektach B", to dlaczego działa? – Tom

+0

@Tom: Wywołanie A * a = new B() działa, ponieważ wywołujemy A :: func(). Wywołanie B * b = nowe B() nie jest kompilowane, ponieważ wywołujesz prywatne B :: func() poza B. – laalto

+0

... poza B lub jego przyjaciółmi. – laalto

Powiązane problemy