Prywatna metoda wirtualna w C ++

125

Jaka jest zaleta uczynienia metody prywatnej wirtualną w C ++?

Zauważyłem to w projekcie C ++ open source:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};
Silverburgh
źródło
9
Myślę, że pytanie jest wstecz. Powód tworzenia czegoś wirtualnego jest zawsze taki sam: zezwolenie klasom pochodnym na nadpisanie tego. Zatem pytanie powinno brzmieć: jaka jest korzyść z uczynienia metody wirtualnej prywatną? Na co odpowiedź brzmi: ustaw domyślnie wszystko jako prywatne. :-)
ShreevatsaR
1
@ShreevatsaR Ale nie odpowiedziałeś nawet na własne pytanie ......
Spencer
@ShreevatsaR Pomyślałem, że masz na myśli odwrócenie się w inny sposób: Jaka jest zaleta uczynienia metody wirtualnej nie prywatną?
Peter - Przywróć Monikę

Odpowiedzi:

116

Herb Sutter bardzo ładnie wyjaśnił to tutaj .

Wskazówka nr 2: wolę, aby funkcje wirtualne były prywatne.

Dzięki temu klasy pochodne zastępują funkcję w celu dostosowania zachowania w razie potrzeby, bez dalszego ujawniania funkcji wirtualnych bezpośrednio przez umożliwienie ich wywoływania przez klasy pochodne (co byłoby możliwe, gdyby funkcje były tylko chronione). Chodzi o to, że istnieją funkcje wirtualne, które umożliwiają dostosowywanie; jeśli nie trzeba ich również wywoływać bezpośrednio z kodu klas pochodnych, nie ma potrzeby, aby były one kiedykolwiek prywatne

Prasoon Saurav
źródło
Jak można się domyślić z mojej odpowiedzi, myślę, że wytyczna nr 3 Suttera raczej wypycha wskazówkę nr 2 przez okno.
Spencer
66

Jeśli metoda jest wirtualna, może zostać zastąpiona przez klasy pochodne, nawet jeśli jest prywatna. Po wywołaniu metody wirtualnej zostanie wywołana zastąpiona wersja.

(W przeciwieństwie do Herba Suttera cytowanego przez Prasoon Saurav w jego odpowiedzi, C ++ FAQ Lite odradza prywatne wirtuale , głównie dlatego, że często dezorientuje ludzi.)

sth
źródło
41
Wygląda na to, że od tego czasu C ++ FAQ Lite zmieniło swoje zalecenie: „ C ++ FAQ poprzednio zalecał używanie chronionych wirtuali, a nie prywatnych wirtuali. Jednak prywatne wirtualne podejście jest teraz na tyle powszechne, że zamieszanie nowicjuszy jest mniejszym problemem.
Zack The Human
19
Jednak niepokój nadal budzi zamieszanie wśród ekspertów. Żaden z czterech profesjonalistów C ++ siedzących obok mnie nie był świadomy istnienia prywatnych wirtuałów.
Newtonx
12

Pomimo wszystkich wezwań do ogłoszenia wirtualnego członka jako prywatnego, argument po prostu nie wytrzymuje. Często przesłonięcie funkcji wirtualnej przez klasę pochodną będzie musiało wywołać wersję klasy bazowej. Nie może, jeśli zostało zadeklarowane private:

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

Ci mają zadeklarować metodę klasy bazowej protected.

Następnie musisz zastosować brzydki cel wskazania za pomocą komentarza, że ​​metoda powinna zostać zastąpiona, ale nie wywoływana.

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

Tak więc wskazówka Herba Suttera nr 3 ... Ale koń i tak wyszedł ze stajni.

Kiedy deklarujesz coś, protectedco niejawnie ufasz autorowi dowolnej klasy pochodnej w zrozumieniu i prawidłowym używaniu chronionych elementów wewnętrznych, tak jak frienddeklaracja sugeruje głębsze zaufanie dla privateczłonków.

Użytkownicy, którzy mają złe zachowanie z powodu naruszenia tego zaufania (np. Oznaczeni jako „nieświadomi”, nie zawracając sobie głowy czytaniem dokumentacji), mogą winić tylko siebie.

Aktualizacja : Otrzymałem opinię, która twierdzi, że można w ten sposób „łączyć” implementacje funkcji wirtualnych za pomocą prywatnych funkcji wirtualnych. Jeśli tak, z pewnością chciałbym to zobaczyć.

Kompilatory C ++, których używam, zdecydowanie nie pozwolą implementacji klasy pochodnej na wywołanie prywatnej implementacji klasy bazowej.

Gdyby komisja C ++ zwolniła „prywatne”, aby zezwolić na ten konkretny dostęp, byłbym całkowicie zwolennikiem prywatnych funkcji wirtualnych. W obecnym stanie nadal radzi się, abyśmy zamknęli drzwi do stodoły po kradzieży konia.

Spencer
źródło
3
Uważam, że twój argument jest nieważny. Jako twórca interfejsu API powinieneś dążyć do uzyskania interfejsu, który jest trudny do nieprawidłowego użycia i nie narażać innego programisty na popełnienie własnych błędów. To, co chcesz zrobić w swoim przykładzie, można zaimplementować tylko prywatnymi metodami wirtualnymi.
sigy
1
Nie o tym mówiłem. Ale możesz zrestrukturyzować swój kod, aby osiągnąć ten sam efekt bez konieczności wywoływania funkcji prywatnej klasy bazowej
sigy
3
W swoim przykładzie chcesz rozszerzyć zachowanie set_data. Instrukcje m_data = ndata;i cleanup();dlatego można je uznać za niezmiennik, który musi obowiązywać dla wszystkich implementacji. Dlatego uczyń cleanup()niewirtualne i prywatne. Dodaj wywołanie do innej metody prywatnej, która jest wirtualna i stanowi punkt rozszerzenia Twojej klasy. Teraz nie ma już potrzeby, aby twoje klasy pochodne wywoływały podstawy cleanup(), twój kod pozostaje czysty, a interfejs jest trudny do nieprawidłowego użycia.
sigy
2
@sigy To tylko przesuwa słupki bramki. Musisz wyjść poza przykład przestępczy. Kiedy są dalsze potomkowie, którzy muszą wywołać wszystkie cleanup()s w łańcuchu, argument się rozpada. A może zalecasz dodatkową funkcję wirtualną dla każdego potomka w łańcuchu? Ick. Nawet Herb Sutter uznał chronione funkcje wirtualne jako lukę w swojej wytycznej nr 3. W każdym razie bez jakiegoś prawdziwego kodu nigdy mnie nie przekonasz.
Spencer,
2
W takim razie zgódźmy się nie zgadzać;)
sigy
9

Po raz pierwszy natknąłem się na tę koncepcję, czytając „Efektywny C ++” Scotta Meyersa, punkt 35: Rozważ alternatywy dla funkcji wirtualnych.Chciałem polecić Scotta Mayersa innym, którzy mogą być zainteresowani.

Jest to część wzorca metody szablonu za pośrednictwem idiomu interfejsu nie-wirtualnego : publiczne metody nie są wirtualne; raczej opakowują wywołania metod wirtualnych, które są prywatne. Klasa bazowa może następnie uruchomić logikę przed i po wywołaniu prywatnej funkcji wirtualnej:

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

Myślę, że to bardzo ciekawy wzorzec projektowy i jestem pewien, że widać, jak przydatna jest dodatkowa kontrola.

  • Po co tworzyć funkcję wirtualną private? Najlepszym powodem jest to, że już udostępniliśmy plikpublic metodę obliczania.
  • Dlaczego po prostu nie zrobić tego protectedtak, żebym mógł użyć tej metody do innych interesujących rzeczy? Przypuszczam, że zawsze będzie to zależeć od twojego projektu i od tego, jak uważasz, że klasa bazowa pasuje. Twierdziłbym, że twórca klas pochodnych powinien skupić się na implementacji wymaganej logiki; wszystko inne jest już załatwione. Jest też kwestia hermetyzacji.

Z punktu widzenia C ++, przesłonięcie prywatnej metody wirtualnej jest całkowicie uzasadnione, nawet jeśli nie będzie można jej wywołać ze swojej klasy. To wspiera projekt opisany powyżej.

Pooven
źródło
3

Używam ich, aby umożliwić klasom pochodnym „wypełnienie luk” dla klasy bazowej bez ujawniania takiej dziury użytkownikom końcowym. Na przykład mam wysoce stanowe obiekty wywodzące się ze wspólnej bazy, która może zaimplementować tylko 2/3 ogólnej maszyny stanu (klasy pochodne zapewniają pozostałą 1/3 w zależności od argumentu szablonu, a podstawa nie może być szablonem dla inne powody).

POTRZEBUJĘ mieć wspólną klasę bazową, aby wiele publicznych interfejsów API działało poprawnie (używam różnorodnych szablonów), ale nie mogę wypuścić tego obiektu na wolność. Co gorsza, jeśli zostawię kratery w maszynie stanowej - w postaci czystych funkcji wirtualnych - gdziekolwiek poza "Prywatnym", pozwolę sprytnemu lub nieświadomemu użytkownikowi pochodzącemu z jednej z jego klas potomnych na przesłonięcie metod, których użytkownicy nigdy nie powinni dotykać. Dlatego umieściłem „mózgi” maszyny stanowej w PRYWATNYCH funkcjach wirtualnych. Następnie bezpośrednie elementy potomne klasy bazowej wypełniają puste miejsca w swoich NON-wirtualnych przesłonięciach, a użytkownicy mogą bezpiecznie używać wynikowych obiektów lub tworzyć własne dalsze klasy pochodne bez obawy o zepsucie maszyny stanowej.

Jeśli chodzi o argument, że nie powinieneś MIEĆ publicznych metod wirtualnych, mówię BS. Użytkownicy mogą w niewłaściwy sposób przesłonić prywatne wirtuale równie łatwo, jak publiczne - w końcu definiują nowe klasy. Jeśli publiczność nie powinna modyfikować danego API, nie czyń go W OGÓLE wirtualnym w publicznie dostępnych obiektach.

Zack Yezek
źródło