2009-01-27 10 views
37

Czy jest jakikolwiek powód, aby uprawnienia do nadpisanej wirtualnej funkcji C++ różniły się od klasy bazowej? Czy jest w tym jakieś niebezpieczeństwo?Zastępowanie publicznych funkcji wirtualnych z prywatnymi funkcjami w C++

Na przykład:

class base { 
    public: 
     virtual int foo(double) = 0; 
} 

class child : public base { 
    private: 
     virtual int foo(double); 
} 

C++ faq mówi, że jest to zły pomysł, ale nie mówi dlaczego.

Widziałem ten idiom w jakimś kodzie i uważam, że autor próbował uczynić klasę ostateczną, opierając się na założeniu, że nie jest możliwe zastąpienie funkcji prywatnego członka. Jednak This article pokazuje przykład nadpisywania prywatnych funkcji. Oczywiście another part of the C++ faq zaleca odrobienie tego.

Moje konkretne pytania:

  1. czy jest jakiś problem techniczny z użyciem innego zezwolenia na wirtualnych metod w klasach pochodnych vs klasy bazowej?

  2. Czy istnieje uzasadniony powód, aby to zrobić?

+3

ponowne wynalezienie "chronionych" jesteśmy? –

Odpowiedz

27

Problem polega na tym, że metody klasy Base są sposobem deklarowania jego interfejsu. Jest to w istocie powiedzenie: "To są rzeczy, które możesz zrobić dla obiektów tej klasy."

Kiedy w klasie pochodnej robisz coś, co Baza zadeklarowała jako publiczną prywatną, zabierasz coś. Teraz, mimo że obiekt Derived" jest-a "Obiektem podstawowym, coś, co powinieneś być w stanie zrobić do obiektu klasy Base, którego nie można zrobić w obiekcie klasy pochodnej, może to spowodować problem "techniczny" w programie? Może nie, ale prawdopodobnie będzie oznaczać, że obiekt klasy nie będzie się zachowywał sposób, w jaki Twoi użytkownicy oczekują, że się zachowają.

Jeśli znajdziesz się w sytuacji, w której jest to pożądane (z wyjątkiem przypadku nieaktualnej metody wymienionej w innej odpowiedzi), s czy masz model dziedziczenia, w którym dziedziczenie tak naprawdę nie modeluje "is-a" (np. Przykład Scotta Myersa Kwadrat dziedziczący po Prostokącie, ale nie można zmienić szerokości Kwadratu niezależnie od jego wysokości, jak w przypadku prostokąta) i może być konieczne ponowne rozważenie relacji klasowych.

6

Nie ma problemu technicznego, ale skończy się sytuacja, w której publicznie dostępne funkcje będą zależeć od tego, czy masz wskaźnik bazowy czy wskaźnik pochodny.

To moim zdaniem byłoby dziwne i mylące.

35

Otrzymujesz zaskakujący wynik, że jeśli masz dziecko, nie możesz wywołać foo, ale możesz rzucić je do bazy, a następnie zadzwonić do foo.

child *c = new child(); 
c->foo; // compile error (can't access private member) 
static_cast<base *>(c)->foo(); // this is fine, but still calls the implementation in child 

Przypuszczam, że możesz być w stanie wymyślić przykład, w którym nie ma funkcji narażone, z wyjątkiem, gdy jesteś traktując ją jako instancja klasy bazowej. Ale sam fakt pojawienia się tej sytuacji sugerowałby zły projekt OO gdzieś po linii, który prawdopodobnie powinien zostać refaktoryzowany.

+0

Qt to zrobił i sprowadził mnie tutaj :) Dzięki. – mlvljr

+3

Również: jako IDE, ssać! – mlvljr

+21

@mlvljr: Ale jako wydarzenie astronomiczne jestem całkiem niesamowity! – Eclipse

3

Można to zrobić, a bardzo rzadko przyniesie korzyści. Na przykład w naszej bazie kodu używamy biblioteki zawierającej klasę z funkcją publiczną, z której korzystaliśmy, ale teraz zniechęcamy do korzystania z niej z powodu innych potencjalnych problemów (istnieją bezpieczniejsze metody wywoływania). Mamy również klasę wywodzącą się z tej klasy, której wiele naszego kodu używa bezpośrednio. Zrobiliśmy więc daną funkcję prywatną w klasie pochodnej, aby pomóc wszystkim pamiętać, aby jej nie używać, jeśli mogą jej pomóc. Nie eliminuje możliwości korzystania z niego, ale przechwytuje niektóre zastosowania, gdy kod próbuje skompilować, a nie później w przeglądach kodu.

1
  1. Brak problemów technicznych, jeśli masz na myśli przez techniczne, ponieważ istnieje ukryty koszt runtime.
  2. Jeśli dziedziczysz bazę publicznie, nie powinieneś tego robić. Jeśli dziedziczysz za pomocą chronionej lub prywatnej, może to zapobiec użyciu metod, które nie mają sensu, chyba że masz wskaźnik bazowy.
4

Może być bardzo przydatny, jeśli używasz prywatnego dziedziczenia - tzn. Chcesz ponownie użyć (dostosowanej) funkcjonalności klasy bazowej, ale nie interfejsu.

1

Dobrym przykładem do prywatnego dziedziczenia jest interfejs zdarzeń Listener/Observer.

Przykładowy kod dla prywatnego obiektu:

class AnimatableListener { 
    public: 
    virtual void Animate(float delta_time); 
}; 

class BouncyBall : public AnimatableListener { 
    public: 
    void TossUp() {} 
    private: 
    void Animate(float delta_time) override { } 
}; 

Niektórzy użytkownicy obiektu chcą funkcjonalności macierzystej i niektórzy chcą funkcjonalności dziecko:

class AnimationList { 
    public: 
    void AnimateAll() { 
     for (auto & animatable : animatables) { 
     // Uses the parent functionality. 
     animatable->Animate(); 
     } 
    } 
    private: 
    vector<AnimatableListener*> animatables; 
}; 

class Seal { 
    public: 
    void Dance() { 
     // Uses unique functionality. 
     ball->TossUp(); 
    } 
    private: 
    BouncyBall* ball; 
}; 

ten sposób AnimationList może pomieścić odniesienie do rodziców i korzysta z funkcji rodzicielskiej. Podczas gdy Seal zawiera odwołanie do elementu podrzędnego i używa unikalnej funkcji podrzędnej oraz ignorowania elementu nadrzędnego. W tym przykładzie Seal nie powinien wywoływać Animate. Teraz, jak wspomniano powyżej, można wywołać Animate przez rzutowanie na obiekt bazowy, ale jest to trudniejsze i na ogół nie powinno się tego robić.

+0

Interfejs odbiornika/obserwatora, odbierający oddzwonienia. – dashesy