Publiczna funkcja wirtualna uzyskana prywatnie w C ++

Próbowałem dowiedzieć się, co się dzieje, gdy klasa pochodna deklaruje funkcję wirtualną jako prywatną. Oto program, który napisałem

#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) wynik był:

B constructor called
B::func called

Czy to nie narusza prywatnego zestawu dostępu dla tej funkcji. Czy to jest 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 nadpisanie wirtualnego członka uniemożliwi dalsze dziedziczenie klas. Nawet to ma problemy. Modyfikowanie powyższego programu w celu uwzględnienia:

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

oraz główny program testowy do:

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

wyjście to:

C::func called
 Tom30 cze 2009, 06:41
Chciałem tylko zwrócić uwagę na tonie może zrób to w java. Gdy metoda jest nadpisywana, musi mieć poziom dostępu równy lub większy niż publiczny niż metoda, która jest nadpisywana. Powodem jest to, że musisz upewnić się, że wersja metody podklasy może zostać wywołana ze wszystkich kontekstów, z których można wywołać wersję superklasy. Interesujące jest to, że jest to dozwolone w C ++.
 Naveen30 cze 2009, 06:55
No cóż ... mówi, że C ++ pozwala ci się zawiesić, jeśli masz na to ochotę.

questionAnswers(3)

To jest dobrze zdefiniowane zachowanie. Jeślia byłyB* to się nie skompiluje. Powodem jest to, że dostęp członków jest rozwiązywany statycznie przez kompilator, a nie dynamicznie w czasie wykonywania. Wiele książek C ++ sugeruje, że należy unikać kodowania w ten sposób, ponieważ myli on mniej doświadczonych programistów.

 Naveen30 cze 2009, 06:52
@Tom: W czasie kompilacji sprawdzana jest semantyka twojego programu. Dzwonisz do func () używając A *, które jest prawidłowym połączeniem. Comipler nie ma możliwości sprawdzenia, czy to wywołanie jest przetwarzane na wywołanie funkcji członka prywatnego w czasie wykonywania. W czasie wykonywania nie ma pojęcia prywatnego lub publicznego, jest to tylko wywołanie funkcji. Jeśli użyłbyś B * zamiast A *, wywołanie func () byłoby nieprawidłowe, a kompilator oznaczyłby go jako błąd.
 Tom30 cze 2009, 07:01
@Naveen: Dziękuję. Przypuszczam, że zapomniałem, że kompilator nie zachował informacji o dostępie w czasie wykonywania ... dla mnie wydaje się to niepożądanym zachowaniem. Zwłaszcza, że ​​możesz po prostu rzucić B na A, aby spróbować uzyskać dostęp do funkcji prywatnej. Teraz to ma sens ... Po prostu tego nie lubię :-).
 rlbond30 cze 2009, 19:19
@Laurence: Pamiętam, że czytałem coś o tym, dlaczego ktoś chciałby to zrobić, i sugestie, aby tego uniknąć. Od tamtej pory zapomniałem, dlaczego to może być przydatne.
 Laurence Gonsalves30 cze 2009, 08:26
Mówienie „unikaj kodowania w ten sposób, ponieważ myli mniej doświadczonych koderów” oznacza, że ​​istnieją powody, dla których warto to zrobić, gdyby nie „mniej doświadczeni kodery”. Są tam?
 Tom30 cze 2009, 06:37
Co masz na myśli, że dostęp członków jest rozwiązywany statycznie przez kompilator? Istotą vtable jest to, że kompilator nie wie, którą funkcję wywołać w czasie kompilacji ... więc musi ją sprawdzić w czasie wykonywania. Możesz wyjaśnić?

Cóż, dzwoniszA::func() który jestpublic choć w aB obiekt, przez który jest nadpisywanyB::func(). Jest to wspólny wzór z następującymi implikacjami:

func nie ma być nazywany pochodnymB przedmioty

func nie można nadpisać w klasach pochodzących zB

 laalto30 cze 2009, 06:47
@Tom: Wywołanie A * a = new B () działa, ponieważ wywołujesz A :: func (). Wywołanie B * b = new B () nie kompiluje się, ponieważ wywołujesz prywatny B :: func () poza B.
 Ashwin30 cze 2009, 07:24
@Tom, @Naveen, @laalto: Zgadzam się, że specyfikatory poziomu dostępu nie są używane w czasie wykonywania i są przeznaczone tylko dla kompilatora, ale VTABLE jest tworzony przez kompilator. Funkcja prywatna nie powinna była zostać dodana do VTABLE, powodując lukę. Nawet dla powyższego stwierdzenia „func nie może być zastąpiony w klasach pochodzących z B” nie działa :( - stworzyłem klasę C dziedziczącą B i utworzyłem w niej publiczny void func () i zrobiłem „A * a = new C ()” Przypisanie i wyjście to „C :: func called” !!!! Może kompilator g ++ jest uszkodzony ...
 Tom30 cze 2009, 06:55
@laalto: Nie rozumiem tego. Ale wydaje się, że Ashwin mówi, że-- func nazywa B :: func. Wydaje się, że łamie to poziom dostępu, ponieważ za każdym razem, gdy chcę wywołać prywatny B :: func, mogę po prostu rzucić B na A, a następnie wywołać func (). Wygląda na to, że nie powinienem być w stanie tego zrobić. Wygląda na to, że mówisz, że celem zmiany poziomu dostępu na prywatny w podklasie jest to, że podklasy nie mogą przesłonić func () ORAZ tak, że jeśli masz coś, co jest statycznie typu B, nie możesz wywołać func ( ). Ale metoda nadal nie jest naprawdę prywatna, jeśli możesz obejść się przez rzutowanie :-(.
 laalto30 cze 2009, 06:48
... poza B lub jego przyjaciółmi.
 Tom30 cze 2009, 06:57
... zasadniczo wydaje się niewłaściwe, że możesz obejść poziomy dostępu poprzez polimorfizm. Jest to dla mnie bardzo sprzeczne z intuicją.
 laalto30 cze 2009, 07:24
@Tom: C ++ i intuicja nie zawsze idą w parze.
 Tom30 cze 2009, 07:32
@Ashwin: Nie jest prawdą, że kompilator nie powinien dodawać funkcji prywatnej do vtable ... istnieją konteksty, z których poprawne jest wywołanie funkcji prywatnej. Jeśli przeniósłeś kod main () do funkcji w klasie B, na przykład wywołanie funkcji prywatnej byłoby poprawne. Wydaje się, że najlepszym sposobem na wymuszenie poziomu dostępu przez kompilator jest zastosowanie podejścia java: nie zezwalaj na wyższy poziom poufności dostępu do metody zastępowanej.
 Tom30 cze 2009, 07:32
@Ashwin: Może powinieneś edytować swoje pierwotne pytanie i dodać swoje ustalenia klasy C?
 Tom30 cze 2009, 06:43
Jeśli powiesz „func nie jest przeznaczony do wywoływania na pochodnych obiektach B”, to dlaczego to działa?
 Ashwin30 cze 2009, 08:23
@Tom: Dzięki. Dodałem to samo. Jak mówisz, podejście Java wydaje się najlepsze. Nie mówiłem, że VTABLE nigdy nie powinien mieć funkcji prywatnych - wystarczy, że powinna istnieć jakaś logika, aby sprawdzić zmiany poziomu dostępu i selektywnie wypełnić VTABLE. Ale zgadzam się, że to by się rozwiało.
QuestionSolution

Zachowanie jest poprawne. Za każdym razem, gdy deklarujesz swoją funkcję jako „wirtualną”, polecasz kompilatorowi wygenerowanie połączenia wirtualnego zamiast bezpośredniego wywołania tej funkcji. Za każdym razem, gdy nadpisujesz 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 używają bezpośrednio klasy potomnej (którzy polegają na interfejsie „dziecka”).

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żna publicznie wyprowadzić B z A (mówiąc „każdy B to A”), ale ukryć część jego interfejsu. Ci, którzy oczekują „A”, muszą otrzymać w pełni funkcjonalny „A”.

 Marc van Leeuwen24 sty 2013, 10:54
Mogę dodać, że wymaganie od kompilatora zakazania wywoływania prywatnej funkcji wirtualnej B przez a-> func prawie wymagałoby niemożliwości. Naruszenie praw dostępu jest sprawdzane w czasie kompilacji, ale dostęp ten odbywa się w czasie wykonywania. A nawet jeśli wypełnisz plik wykonywalny sprawdzeniami w czasie wykonywania, czy naprawdę byłoby pożądane, abyużytkownik programu widzi zatrzymanie programu z błędem naruszenia dostępu?
 Tom30 cze 2009, 17:01
Dzięki, SadSido. Wydaje się, że jesteś jedynym, który zajął się „publicznymi”. Właściwie zapomniałem, że było coś więcej niż tylko dziedzictwo publiczne, ponieważ zawsze używam publicznego dziedzictwa. To charakter publicznego dziedzictwa pozwala na takie zachowanie - co ma większy sens. Myślałem, że to najlepsze wyjaśnienie. Dziękuję Ci.
 Sharadh27 cze 2014, 06:59
Dodając do tego - rozważ alternatywę. Jeśli interfejs klasy bazowej jest modyfikowany przez klasę pochodną, ​​funkcje polegające na interfejsach mogą ulec awarii. Na przykład powiedzmyvoid doFunc (A* ptr) { ptr->func(); } został napisany, gdy napisano A. Następnie B rozszerza A i modyfikuje jego interfejs. OdB* JESTA*, kompilator pozwala nam przekazać doFunc (& bObj). Spowoduje to awarię, jeśli B może zmienić tryb dostępu func (). W efekcie programowanie za pomocą interfejsów jest tego przyczyną.
 Ashwin30 cze 2009, 10:59
Zgadzam się. Tyle, że kompilator przekształcający funkcję prywatną w publiczny wydawał się nie do przyjęcia. Ale prawdą jest, że „nie można publicznie wyprowadzić B z A (mówiąc„ każdy B to A ”), ale ukryć część jego interfejsu. Ci, którzy oczekują„ A ”, muszą otrzymać w pełni funkcjonalny„ A ”.” Dziękuję za wyjaśnienie.
 SadSido30 cze 2009, 21:42
Dziękuję wam wszystkim. Jest jednak jedna kwestia dotycząca tego pytania, która mnie niepokoi. Mogę mieć wskaźnik do obiektu Child i wywołać jego prywatną metodę po prostu przez static_casting do obiektu nadrzędnego ... Cóż, zgaduję, że static_cast w tym przypadku oznacza "polegać na interfejsie nadrzędnym" ...

yourAnswerToTheQuestion