Jaki jest sens prywatnej, czystej funkcji wirtualnej?

139

W pliku nagłówkowym natknąłem się na następujący kod:

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

Dla mnie oznacza to, że albo Engineklasa, albo klasa pochodna od niej, musi zapewnić implementację tych czystych funkcji wirtualnych. Ale nie sądziłem, że klasy pochodne mogą mieć dostęp do tych prywatnych funkcji w celu ich ponownego zaimplementowania - więc po co je wirtualizować?

BeeBand
źródło

Odpowiedzi:

209

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 Engineklasy 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 Engineczłonków, oświadczenie:

myV8->SetState(5, true);

wywoła void SetState( int var, int val )z klasy pochodnej, konwertując truena 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*/}
};
Maciej Hehl
źródło
dlaczego funkcja wirtualna musi być prywatna? Czy to może być publiczne?
Rich
Zastanawiam się, czy wskazówki podane przez Herba Suttera w jego artykule o wirtualności są nadal aktualne?
nurabha
@Rich Mógłbyś, ale czyniąc je niepublicznymi, możesz wyraźniej przekazać ich intencje. Po pierwsze, pokazuje rozdzielenie obaw, jeśli będziesz trzymać się upublicznienia interfejsu, a implementację niepubliczną. Po drugie, jeśli chcesz, aby dziedziczące klasy mogły wywoływać podstawowe implementacje, możesz zadeklarować je jako chronione; jeśli chcesz, aby dostarczały swoje własne implementacje bez wywoływania podstawowych, ustawiasz je jako prywatne.
Dan
43

Prywatna czysta funkcja wirtualna jest podstawą idiomu interfejsu niewirtualnego (OK, nie jest to absolutnie zawsze czysto wirtualne, ale nadal wirtualne). Oczywiście jest to również używane do innych rzeczy, ale uważam to za najbardziej przydatne (: w dwóch słowach: w funkcji publicznej możesz umieścić kilka typowych rzeczy (takich jak rejestrowanie, statystyki itp.) Na początku i na końcu funkcji, a następnie „w środku”, aby wywołać tę prywatną funkcję wirtualną, która będzie inna dla określonej klasy pochodnej. Coś w rodzaju:

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

Czysto wirtualne - po prostu zobowiązuje klasy pochodne do jej implementacji.

EDYCJA : Więcej na ten temat: Wikipedia :: NVI-idiom

Kiril Kirov
źródło
17

Po pierwsze, pozwoliłoby to klasie pochodnej na zaimplementowanie funkcji, którą może wywołać klasa bazowa (zawierająca deklarację czystej funkcji wirtualnej).

Michael Goldshteyn
źródło
5
że tylko klasa bazowa może wywoływać!
underscore_d
4

EDYCJA: Wyjaśnione stwierdzenia dotyczące możliwości przesłonięcia i możliwości dostępu / wywołania.

Będzie mógł zastąpić te funkcje prywatne. Na przykład następujący wymyślony przykład działa ( EDIT: uczynił metodę klasy pochodnej prywatną i porzuć wywołanie metody klasy pochodnej, main()aby lepiej zademonstrować cel użycia wzorca projektowego ):

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtualmetody w klasie bazowej, takie jak te w kodzie, są zwykle używane do implementowania wzorca projektowego metody szablonu . Ten wzorzec projektowy pozwala na zmianę zachowania algorytmu w klasie bazowej bez zmiany kodu w klasie bazowej. Powyższy kod, w którym metody klasy bazowej są wywoływane za pośrednictwem wskaźnika klasy bazowej, jest prostym przykładem wzorca Template Method.

Unieważnić
źródło
Rozumiem, ale jeśli klasy pochodne i tak mają pewien rodzaj dostępu, po co zawracać sobie głowę ich prywatnością?
BeeBand
@BeeBand: użytkownik miałby dostęp do przesłonięć metody wirtualnej publicznej klasy pochodnej, ale nie miałby dostępu do klas bazowych. W tym przypadku autor klasy pochodnej mógłby również zachować prywatność przesłonięć metody wirtualnej. W rzeczywistości dokonam zmiany w powyższym przykładowym kodzie, aby to podkreślić. Tak czy inaczej, zawsze mogliby dziedziczyć publicznie i przesłonić prywatne metody wirtualne klasy bazowej, ale nadal mieliby dostęp tylko do własnych metod wirtualnych klasy pochodnej. Zauważ, że robię rozróżnienie między przesłonięciem a dostępem / wywołaniem.
Nieważne
ponieważ się mylisz. widoczność dziedziczenia między klasami Enginei DerivedEnginenie ma nic wspólnego z tym, co DerivedEnginemoże lub nie może przesłonić (lub uzyskać dostęp, jeśli o to chodzi).
wilhelmtell
@wilhelmtell: westchnienie Masz oczywiście rację. Zaktualizuję odpowiednio moją odpowiedź.
Nieważne
3

Prywatna metoda wirtualna służy do ograniczania liczby klas pochodnych, które mogą przesłonić daną funkcję. Klasy pochodne, które muszą przesłonić prywatną metodę wirtualną, będą musiały być przyjacielem klasy bazowej.

Krótkie wyjaśnienie można znaleźć w witrynie DevX.com .


EDYCJA Prywatna metoda wirtualna jest efektywnie używana we wzorcu metody szablonowej . Klasy pochodne mogą przesłonić prywatną metodę wirtualną, ale klasy pochodne nie mogą wywołać prywatnej metody wirtualnej klasy bazowej (w naszym przykładzie SetStateBooli SetStateInt). Tylko klasa bazowa może skutecznie wywołać swoją prywatną metodę wirtualną ( tylko jeśli klasy pochodne muszą wywoływać podstawową implementację funkcji wirtualnej, należy zabezpieczyć funkcję wirtualną ).

Ciekawy artykuł można znaleźć na temat wirtualności .

Buhake Sindi
źródło
2
@Gentleman ... hmmm przewiń w dół do komentarza Colina D. Bennetta. Wydaje się, że myśli, że „Prywatna funkcja wirtualna może zostać przesłonięta przez klasy pochodne, ale może zostać wywołana tylko z poziomu klasy bazowej”. @Michael Goldshteyn też tak myśli.
BeeBand
Przypuszczam, że zapomniałeś o zasadzie, że klasa prywatna nie może być widziana przez klasę pochodną. To są zasady OOP i mają zastosowanie do wszystkich języków, które są OOP. Aby klasa pochodna mogła zaimplementować swoją prywatną metodę wirtualną klasy bazowej, musi być friendklasą bazową. Qt przyjął to samo podejście, kiedy zaimplementował swój model dokumentu XML DOM.
Buhake Sindi
@Gentleman: Nie, nie zapomniałem. Zrobiłem literówkę w moim komentarzu. Zamiast „dostępu do metod klasy bazowej” powinienem napisać „może przesłonić metody klasy bazowej”. Klasa pochodna może z pewnością przesłonić metodę prywatnej wirtualnej klasy bazowej, nawet jeśli nie może uzyskać dostępu do tej metody klasy bazowej. Wskazany artykuł DevX.com był nieprawidłowy (dziedziczenie publiczne). Wypróbuj kod w mojej odpowiedzi. Pomimo metody prywatnej wirtualnej klasy bazowej, klasa pochodna może ją przesłonić. Nie mylmy możliwości przesłonięcia prywatnej metody wirtualnej klasy bazowej z możliwością jej wywołania.
Nieważne
@Gentleman: @wilhelmtell wskazał błąd w mojej odpowiedzi / komentarzu. Moje twierdzenie o dziedziczeniu wpływającym na dostępność klasy pochodnej metody klasy bazowej było wyłączone. Usunąłem obraźliwy komentarz do Twojej odpowiedzi.
Nieważne
@Void, widzę, że klasa pochodna może przesłonić swoją prywatną metodę wirtualną klasy bazowej, ale nie może jej użyć. Jest to więc zasadniczo wzorzec metody szablonowej.
Buhake Sindi
0

Odpowiedź TL; DR:

Możesz traktować to jako inny poziom hermetyzacji - gdzieś między chronionym a prywatnym : nie możesz go wywołać z klasy podrzędnej, ale możesz go zastąpić.

Jest to przydatne przy implementacji wzorca projektowego Template Method . Możesz używać chronionych , ale prywatnych razem z wirtualnymi, które mogą być uznane za lepszy wybór ze względu na lepszą hermetyzację.

jaskmar
źródło