Pytanie w temacie sugeruje dość powszechne zamieszanie. Zamieszanie jest na tyle powszechne, że C ++ FAQ od dawna opowiadał się przeciwko używaniu prywatnych wirtualiów, ponieważ zamieszanie wydawało się czymś złym.
Aby najpierw pozbyć się zamieszania: tak, prywatne funkcje wirtualne można przesłonić w klasach pochodnych. Metody klas pochodnych nie mogą wywoływać funkcji wirtualnych z klasy bazowej, ale mogą zapewnić dla nich własną implementację. Według Herba Suttera posiadanie publicznego niewirtualnego interfejsu w klasie bazowej oraz prywatnej implementacji, którą można dostosowywać w klasach pochodnych, pozwala na lepsze „oddzielenie specyfikacji interfejsu od specyfikacji dostosowywalnego zachowania implementacji”. Więcej na ten temat można przeczytać w jego artykule „Wirtualność” .
W przedstawionym kodzie jest jednak jeszcze jedna interesująca rzecz, która moim zdaniem zasługuje na więcej uwagi. Interfejs publiczny składa się z zestawu przeciążonych funkcji niewirtualnych, które wywołują niepubliczne, niezciążone funkcje wirtualne. Jak zwykle w świecie C ++ jest to idiom, ma nazwę i oczywiście jest przydatny. Nazywam się (niespodzianka, niespodzianka!)
„Public Overloaded Non-Virtuals Call Protected Non-Overloaded Virtuals”
Pomaga odpowiednio zarządzać regułą ukrywania . Możesz przeczytać więcej na ten temat tutaj , ale postaram się to wkrótce wyjaśnić.
Wyobraź sobie, że funkcje wirtualne Engine
klasy są jednocześnie jej interfejsem i jest to zestaw przeciążonych funkcji, które nie są czysto wirtualne. Gdyby były czysto wirtualne, nadal można by napotkać ten sam problem, jak opisano poniżej, ale niżej w hierarchii klas.
class Engine
{
public:
virtual void SetState( int var, bool val ) {/*some implementation*/}
virtual void SetState( int var, int val ) {/*some implementation*/}
};
Teraz załóżmy, że chcesz utworzyć klasę pochodną i musisz podać nową implementację tylko dla metody, która przyjmuje dwa argumenty typu int.
class MyTurbochargedV8 : public Engine
{
public:
// To prevent SetState( int var, bool val ) from the base class,
// from being hidden by the new implementation of the other overload (below),
// you have to put using declaration in the derived class
using Engine::SetState;
void SetState( int var, int val ) {/*new implementation*/}
};
Jeśli zapomniałeś umieścić deklarację using w klasie pochodnej (lub przedefiniować drugie przeciążenie), możesz mieć kłopoty w poniższym scenariuszu.
MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);
Jeśli nie zapobiegłeś ukrywaniu Engine
członków, oświadczenie:
myV8->SetState(5, true);
wywoła void SetState( int var, int val )
z klasy pochodnej, konwertując true
na int
.
Jeśli interfejs nie jest wirtualny, a wirtualna implementacja jest niepubliczna, jak w Twoim przykładzie, autor klasy pochodnej ma o jeden problem mniej do przemyślenia i może po prostu napisać
class MyTurbochargedV8 : public Engine
{
private:
void SetStateInt(int var, int val ) {/*new implementation*/}
};