Was ist der Sinn einer privaten, rein virtuellen Funktion?

139

In einer Header-Datei bin ich auf folgenden Code gestoßen:

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;    
};

Für mich bedeutet dies, dass entweder die EngineKlasse oder eine davon abgeleitete Klasse die Implementierung für diese rein virtuellen Funktionen bereitstellen muss. Ich dachte jedoch nicht, dass abgeleitete Klassen Zugriff auf diese privaten Funktionen haben könnten, um sie erneut zu implementieren. Warum sollten sie also virtuell gemacht werden?

BeeBand
quelle

Antworten:

209

Die Frage im Thema deutet auf eine ziemlich häufige Verwirrung hin. Die Verwirrung ist häufig genug, dass C ++ FAQ lange Zeit gegen die Verwendung privater Virtuals plädierte, weil Verwirrung eine schlechte Sache zu sein schien.

Um die Verwirrung zuerst zu beseitigen: Ja, private virtuelle Funktionen können in den abgeleiteten Klassen überschrieben werden. Methoden abgeleiteter Klassen können keine virtuellen Funktionen aus der Basisklasse aufrufen, sie können jedoch ihre eigene Implementierung für sie bereitstellen. Laut Herb Sutter ermöglicht eine öffentliche nicht virtuelle Schnittstelle in der Basisklasse und eine private Implementierung, die in den abgeleiteten Klassen angepasst werden kann, eine bessere "Trennung der Schnittstellenspezifikation von der Spezifikation des anpassbaren Verhaltens der Implementierung". Mehr dazu lesen Sie in seinem Artikel "Virtualität" .

Der von Ihnen vorgestellte Code enthält jedoch noch eine weitere interessante Sache, die meiner Meinung nach etwas mehr Aufmerksamkeit verdient. Die öffentliche Schnittstelle besteht aus einer Reihe überladener nicht virtueller Funktionen, und diese Funktionen rufen nicht öffentliche, nicht überladene virtuelle Funktionen auf. Wie in der C ++ - Welt üblich, ist es eine Redewendung, es hat einen Namen und natürlich ist es nützlich. Der Name ist (Überraschung, Überraschung!)

"Öffentliche überladene Nicht-Virtuals rufen geschützte nicht überladene Virtuals auf"

Es hilft , die Ausblendungsregel richtig zu verwalten . Sie können mehr darüber lesen hier , aber ich werde versuchen , es kurz zu erklären.

Stellen Sie sich vor, dass virtuelle Funktionen der EngineKlasse auch ihre Schnittstelle sind und es sich um eine Reihe überladener Funktionen handelt, die nicht rein virtuell sind. Wenn sie rein virtuell wären, könnte man immer noch auf dasselbe Problem stoßen, wie unten beschrieben, jedoch niedriger in der Klassenhierarchie.

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

Nehmen wir nun an, Sie möchten eine abgeleitete Klasse erstellen und müssen nur für die Methode eine neue Implementierung bereitstellen, die zwei Ints als Argumente verwendet.

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*/}
};

Wenn Sie vergessen haben, die using-Deklaration in die abgeleitete Klasse einzufügen (oder die zweite Überladung neu zu definieren), können im folgenden Szenario Probleme auftreten.

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

Wenn Sie das Verstecken der EngineMitglieder nicht verhindert haben, lautet die Aussage:

myV8->SetState(5, true);

nennen würde void SetState( int var, int val )von der abgeleiteten Klasse, die Umwandlung truezu int.

Wenn die Schnittstelle nicht virtuell ist und die virtuelle Implementierung nicht öffentlich ist, wie in Ihrem Beispiel, hat der Autor der abgeleiteten Klasse ein Problem weniger zu denken und kann einfach schreiben

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};
Maciej Hehl
quelle
Warum muss die virtuelle Funktion privat sein? Kann es öffentlich sein?
Rich
Ich frage mich, ob die Richtlinien, die Herb Sutter in seinem Artikel "Virtualität" gegeben hat, noch heute gelten.
Nurabha
@Rich Sie könnten, aber indem Sie sie nicht öffentlich machen, können Sie ihre Absicht klarer vermitteln. Erstens zeigt es eine Trennung von Bedenken, wenn Sie sich daran halten, die Schnittstelle öffentlich und die Implementierung nicht öffentlich zu machen. Zweitens, wenn Sie möchten, dass erbende Klassen die Basisimplementierungen aufrufen können, können Sie sie als geschützt deklarieren. Wenn Sie nur möchten, dass sie ihre eigenen Implementierungen bereitstellen, ohne die Basisimplementierungen aufzurufen, machen Sie sie privat.
Dan
43

Private reine virtuelle Funktion ist die Basis der nicht-virtuellen Schnittstellensprache (OK, es ist nicht absolut immer rein virtuell, aber immer noch virtuell). Natürlich wird dies auch für andere Dinge verwendet, aber ich finde dies am nützlichsten (: In zwei Worten: In einer öffentlichen Funktion könnten Sie einige allgemeine Dinge (wie Protokollierung, Statistik usw.) am Anfang und setzen Am Ende der Funktion und dann "in der Mitte", um diese private virtuelle Funktion aufzurufen, ist dies für die jeweilige abgeleitete Klasse unterschiedlich. So etwas wie:

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()
{
    // ..
}

Rein virtuell - verpflichtet nur die abgeleiteten Klassen, es zu implementieren.

EDIT : Mehr dazu: Wikipedia :: NVI-idiom

Kiril Kirov
quelle
17

Zum einen würde dies einer abgeleiteten Klasse ermöglichen, eine Funktion zu implementieren, die die Basisklasse (die die Deklaration der reinen virtuellen Funktion enthält) aufrufen kann.

Michael Goldshteyn
quelle
5
das kann nur die Basisklasse aufrufen!
underscore_d
4

BEARBEITEN: Klare Aussagen über die Fähigkeit zum Überschreiben und die Fähigkeit zum Zugreifen / Aufrufen.

Diese privaten Funktionen können überschrieben werden. Das folgende erfundene Beispiel funktioniert beispielsweise ( BEARBEITEN: Abgeleitete Klassenmethode privat machen und Aufruf der abgeleiteten Klassenmethode ablegen main(), um die Absicht des verwendeten Entwurfsmusters besser zu demonstrieren. ):

#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 virtualMethoden in einer Basisklasse wie die in Ihrem Code werden normalerweise zum Implementieren des Entwurfsmusters für Vorlagenmethoden verwendet . Dieses Entwurfsmuster ermöglicht es, das Verhalten eines Algorithmus in der Basisklasse zu ändern, ohne den Code in der Basisklasse zu ändern. Der obige Code, in dem die Basisklassenmethoden über einen Basisklassenzeiger aufgerufen werden, ist ein einfaches Beispiel für das Muster der Vorlagenmethode.

Leere
quelle
Ich verstehe, aber wenn abgeleitete Klassen sowieso eine Art Zugriff haben, warum sollte man sie dann privat machen?
BeeBand
@BeeBand: Der Benutzer hätte Zugriff auf die Überschreibungen der virtuellen Methode der öffentlichen abgeleiteten Klasse, aber keinen Zugriff auf die Basisklassen. Der Autor der abgeleiteten Klasse könnte in diesem Fall die Überschreibungen der virtuellen Methode auch privat halten. Tatsächlich werde ich die Änderung im obigen Beispielcode vornehmen, um dies hervorzuheben. In beiden Fällen könnten sie immer öffentlich erben und die virtuellen Methoden der privaten Basisklasse überschreiben, aber sie hätten immer noch nur Zugriff auf ihre eigenen abgeleiteten virtuellen Methoden der Klasse. Beachten Sie, dass ich zwischen Überschreiben und Zugriff / Aufruf unterscheide.
Nichtig
weil du falsch liegst. die Vererbung Sichtbarkeit zwischen den Klassen Engineund DerivedEnginehat nichts mit dem zu tun , was DerivedEnginekann oder nicht außer Kraft setzen (oder Access, für diese Angelegenheit).
Wilhelmtell
@wilhelmtell: seufzen Sie sind natürlich richtig. Ich werde meine Antwort entsprechend aktualisieren.
Nichtig
3

Die private virtuelle Methode wird verwendet, um die Anzahl der abgeleiteten Klassen zu begrenzen, die die angegebene Funktion überschreiben können. Die abgeleiteten Klassen, die die private virtuelle Methode überschreiben müssen, müssen mit der Basisklasse befreundet sein.

Eine kurze Erklärung finden Sie auf DevX.com .


BEARBEITEN Eine private virtuelle Methode wird effektiv im Muster der Vorlagenmethode verwendet . Die abgeleiteten Klassen können die private virtuelle Methode überschreiben, aber die abgeleiteten Klassen können die private virtuelle Methode der Basisklasse nicht aufrufen (in Ihrem Beispiel SetStateBoolund SetStateInt). Nur die Basisklasse kann ihre private virtuelle Methode effektiv aufrufen ( Nur wenn abgeleitete Klassen die Basisimplementierung einer virtuellen Funktion aufrufen müssen, machen Sie die virtuelle Funktion geschützt ).

Ein interessanter Artikel über Virtualität ist zu finden .

Buhake Sindi
quelle
2
@ Gentleman ... hmmm scrolle runter zu Colin D Bennetts Kommentar. Er scheint zu denken, dass "eine private virtuelle Funktion von abgeleiteten Klassen überschrieben werden kann, aber nur innerhalb der Basisklasse aufgerufen werden kann." @ Michael Goldshteyn denkt auch so.
BeeBand
Ich denke, Sie haben das Prinzip vergessen, dass eine privatbasierte Klasse von ihrer abgeleiteten Klasse nicht gesehen werden kann. Dies sind die OOP-Regeln und gelten für alle Sprachen, die OOP sind. Damit eine abgeleitete Klasse ihre private virtuelle Methode friendder Basisklasse implementieren kann, muss sie zur Basisklasse gehören. Qt hat den gleichen Ansatz gewählt, als sie ihr XML-DOM-Dokumentmodell implementiert haben.
Buhake Sindi
@ Gentleman: Nein, ich habe nicht vergessen. Ich habe in meinem Kommentar einen Tippfehler gemacht. Anstelle von "Zugriff auf die Basisklassenmethoden" hätte ich schreiben sollen "Kann die Basisklassenmethoden überschreiben". Die abgeleitete Klasse kann die private virtuelle Basisklassenmethode sicherlich überschreiben, selbst wenn sie nicht auf diese Basisklassenmethode zugreifen kann. Der DevX.com-Artikel, auf den Sie hingewiesen haben, war falsch (öffentliche Vererbung). Probieren Sie den Code in meiner Antwort aus. Trotz der privaten virtuellen Basisklassenmethode kann die abgeleitete Klasse diese überschreiben. Verwechseln wir nicht die Möglichkeit, eine private virtuelle Basisklassenmethode mit der Möglichkeit zu überschreiben, sie aufzurufen.
Nichtig
@ Gentleman: @ Wilhelmtell hat in meiner Antwort / meinem Kommentar auf den Fehler hingewiesen. Meine Behauptung über die Vererbung, die die Zugänglichkeit abgeleiteter Klassen der Basisklassenmethode beeinflusst, war falsch. Ich habe den beleidigenden Kommentar zu Ihrer Antwort entfernt.
Nichtig
@Void, ich sehe, dass eine abgeleitete Klasse ihre private virtuelle Methode der Basisklasse überschreiben kann , sie aber nicht verwenden kann. Dies ist also im Wesentlichen ein Muster der Vorlagenmethode.
Buhake Sindi
0

TL; DR Antwort:

Sie können es wie eine andere Kapselungsebene behandeln - irgendwo zwischen geschützt und privat : Sie können es nicht aus der untergeordneten Klasse aufrufen, aber Sie können es überschreiben.

Dies ist nützlich, wenn Sie das Entwurfsmuster der Vorlagenmethode implementieren . Sie könnten geschützt verwenden , aber privat zusammen mit virtuell kann aufgrund der besseren Kapselung als bessere Wahl angesehen werden.

Jaskmar
quelle