Private virtuelle Methode in C ++

125

Was ist der Vorteil, wenn eine private Methode in C ++ virtuell gemacht wird?

Ich habe dies in einem Open Source C ++ - Projekt bemerkt:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};
Silverburgh
quelle
9
Ich denke, die Frage ist rückwärts. Der Grund, etwas virtuell zu machen, ist immer derselbe: abgeleitete Klassen können es überschreiben. Die Frage sollte also lauten: Was ist der Vorteil, eine virtuelle Methode privat zu machen? Darauf lautet die Antwort: Machen Sie standardmäßig alles privat. :-)
ShreevatsaR
1
@ShreevatsaR Aber Sie haben nicht einmal Ihre eigene Frage beantwortet ......
Spencer
@ShreevatsaR Ich dachte, Sie meinen rückwärts anders: Was ist der Vorteil, wenn eine virtuelle Methode nicht privat ist?
Peter - Stellen Sie Monica

Antworten:

115

Herb Sutter hat es hier sehr schön erklärt .

Richtlinie 2: Machen Sie virtuelle Funktionen lieber privat.

Auf diese Weise können die abgeleiteten Klassen die Funktion überschreiben, um das Verhalten nach Bedarf anzupassen, ohne die virtuellen Funktionen direkt offen zu legen, indem sie von abgeleiteten Klassen aufgerufen werden (wie dies möglich wäre, wenn die Funktionen nur geschützt wären). Der Punkt ist, dass virtuelle Funktionen vorhanden sind, um die Anpassung zu ermöglichen. Sofern sie nicht auch direkt aus dem Code abgeleiteter Klassen aufgerufen werden müssen, müssen sie niemals etwas anderes als privat gemacht werden

Prasoon Saurav
quelle
Wie Sie meiner Antwort entnehmen können, schiebt Sutters Richtlinie Nr. 3 die Richtlinie Nr. 2 eher aus dem Fenster.
Spencer
66

Wenn die Methode virtuell ist, kann sie von abgeleiteten Klassen überschrieben werden, auch wenn sie privat ist. Wenn die virtuelle Methode aufgerufen wird, wird die überschriebene Version aufgerufen.

(Im Gegensatz zu Herb Sutter, der von Prasoon Saurav in seiner Antwort zitiert wurde, empfiehlt das C ++ FAQ Lite gegen private Virtuals , hauptsächlich weil es Menschen oft verwirrt.)

etw
quelle
41
Es scheint, dass das C ++ FAQ Lite seitdem seine Empfehlung geändert hat: "In den C ++ FAQ wurde früher empfohlen, geschützte Virtuals anstelle von privaten Virtuals zu verwenden. Der private virtuelle Ansatz ist jedoch mittlerweile so verbreitet, dass die Verwirrung von Anfängern weniger Anlass zur Sorge gibt. "
Zack The Mensch
19
Die Verwirrung der Experten bleibt jedoch ein Problem. Keiner der vier C ++ - Profis, die neben mir saßen, kannte private Virtuals.
Newtonx
11

Trotz aller Aufrufe, ein virtuelles Mitglied für privat zu erklären, hält das Argument einfach kein Wasser. Häufig muss die Überschreibung einer virtuellen Funktion durch eine abgeleitete Klasse die Basisklassenversion aufrufen. Es kann nicht, wenn es deklariert ist 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) {}
};

Sie haben die Basisklassenmethode zu erklären protected.

Dann müssen Sie das hässliche Mittel nehmen, über einen Kommentar anzuzeigen, dass die Methode überschrieben, aber nicht aufgerufen werden soll.

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

Also Herb Sutters Richtlinie Nr. 3 ... Aber das Pferd ist trotzdem aus dem Stall.

Wenn Sie etwas deklarieren, dem protectedSie implizit vertrauen, dass der Verfasser einer abgeleiteten Klasse die geschützten Interna versteht und ordnungsgemäß verwendet, friendimpliziert eine Deklaration ein tieferes Vertrauen für die privateMitglieder.

Benutzer, die sich schlecht verhalten, wenn sie dieses Vertrauen verletzen (z. B. als "ahnungslos" bezeichnet, weil sie sich nicht die Mühe machen, Ihre Dokumentation zu lesen), sind selbst schuld.

Update : Ich habe einige Rückmeldungen erhalten, die besagen, dass Sie Implementierungen virtueller Funktionen auf diese Weise mithilfe privater virtueller Funktionen "verketten" können. Wenn ja, würde ich es gerne sehen.

Die von mir verwendeten C ++ - Compiler lassen eine abgeleitete Klassenimplementierung definitiv keine private Basisklassenimplementierung aufrufen.

Wenn das C ++ - Komitee "privat" lockern würde, um diesen spezifischen Zugriff zu ermöglichen, wäre ich alles für private virtuelle Funktionen. So wie es aussieht, wird uns immer noch geraten, das Scheunentor zu verschließen, nachdem das Pferd gestohlen wurde.

Spencer
quelle
3
Ich finde dein Argument ungültig. Sie als Entwickler einer API sollten sich um eine Schnittstelle bemühen, die schwer falsch zu verwenden ist, und keinen anderen Entwickler für Ihre eigenen Fehler einrichten. Was Sie in Ihrem Beispiel tun möchten, kann nur mit privaten virtuellen Methoden implementiert werden.
Sigy
1
Das habe ich nicht gesagt. Aber Sie können Ihren Code umstrukturieren, um den gleichen Effekt zu erzielen, ohne eine private Basisklassenfunktion aufrufen zu müssen
Sigy
3
In Ihrem Beispiel möchten Sie das Verhalten von erweitern set_data. Anweisungen m_data = ndata;und cleanup();könnte daher als Invariante betrachtet werden, die für alle Implementierungen gelten muss. Machen Sie deshalb cleanup()nicht virtuell und privat. Fügen Sie einen Aufruf zu einer anderen privaten Methode hinzu, die virtuell und der Erweiterungspunkt Ihrer Klasse ist. Jetzt müssen Ihre abgeleiteten Klassen keine Basen cleanup()mehr aufrufen , Ihr Code bleibt sauber und Ihre Benutzeroberfläche ist schwer falsch zu verwenden.
Sigy
2
@sigy Das bewegt nur die Torpfosten. Sie müssen über das kriminelle Beispiel hinausblicken. Wenn es weitere Nachkommen gibt, die alle cleanup()s in der Kette aufrufen müssen, fällt das Argument auseinander. Oder empfehlen Sie eine zusätzliche virtuelle Funktion für jeden Nachkommen in der Kette? Ick. Sogar Herb Sutter erlaubte geschützte virtuelle Funktionen als Lücke in seiner Richtlinie Nr. 3. Wie auch immer, ohne einen tatsächlichen Code wirst du mich nie überzeugen.
Spencer
2
Dann lasst uns zustimmen, nicht zuzustimmen;)
Sigy
9

Ich bin zum ersten Mal auf dieses Konzept gestoßen, als ich Scott Meyers '' Effective C ++ ', Punkt 35: Alternativen zu virtuellen Funktionen in Betracht gezogen habe. Ich wollte Scott Mayers für andere, die interessiert sein könnten, referenzieren.

Es ist Teil des Template-Methodenmusters über die nicht-virtuelle Schnittstellensprache : Die öffentlich zugänglichen Methoden sind nicht virtuell. Sie verpacken vielmehr die virtuellen Methodenaufrufe, die privat sind. Die Basisklasse kann dann vor und nach dem privaten virtuellen Funktionsaufruf Logik ausführen:

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

Ich denke, dass dies ein sehr interessantes Entwurfsmuster ist und ich bin sicher, dass Sie sehen können, wie nützlich das hinzugefügte Steuerelement ist.

  • Warum die virtuelle Funktion machen private? Der beste Grund ist, dass wir bereits eine publicVerblendungsmethode bereitgestellt haben .
  • Warum nicht einfach so machen protected, dass ich die Methode für andere interessante Dinge verwenden kann? Ich nehme an, es wird immer von Ihrem Design abhängen und davon, wie Sie glauben, dass die Basisklasse passt. Ich würde argumentieren, dass sich der abgeleitete Klassenhersteller auf die Implementierung der erforderlichen Logik konzentrieren sollte. alles andere ist schon erledigt. Es geht auch um die Einkapselung.

Aus C ++ - Sicht ist es völlig legitim, eine private virtuelle Methode zu überschreiben, obwohl Sie sie nicht aus Ihrer Klasse aufrufen können. Dies unterstützt das oben beschriebene Design.

Pooven
quelle
3

Ich verwende sie, um abgeleiteten Klassen zu ermöglichen, die Lücken für eine Basisklasse zu füllen, ohne Endbenutzern eine solche Lücke aufzudecken. Zum Beispiel habe ich hochgradig statusbehaftete Objekte, die von einer gemeinsamen Basis abgeleitet sind und nur 2/3 der gesamten Zustandsmaschine implementieren können (die abgeleiteten Klassen stellen die verbleibenden 1/3 abhängig von einem Vorlagenargument bereit, und die Basis kann keine Vorlage für sein andere Gründe).

Ich MUSS die gemeinsame Basisklasse haben, damit viele der öffentlichen APIs korrekt funktionieren (ich verwende verschiedene Vorlagen), aber ich kann dieses Objekt nicht in die Wildnis lassen. Schlimmer noch, wenn ich die Krater in der Zustandsmaschine - in Form von reinen virtuellen Funktionen - irgendwo anders als in "Privat" belasse, erlaube ich einem klugen oder ahnungslosen Benutzer, der aus einer seiner untergeordneten Klassen stammt, Methoden zu überschreiben, die Benutzer niemals berühren sollten. Also habe ich das Gehirn der Zustandsmaschine in private PRIVATE Funktionen gesteckt. Dann füllen die unmittelbaren untergeordneten Elemente der Basisklasse die Lücken ihrer NICHT-virtuellen Überschreibungen aus, und Benutzer können die resultierenden Objekte sicher verwenden oder ihre eigenen weiteren abgeleiteten Klassen erstellen, ohne sich Gedanken über das Durcheinander der Zustandsmaschine machen zu müssen.

Was das Argument betrifft, dass Sie keine öffentlichen virtuellen Methoden haben sollten, sage ich BS. Benutzer können private Virtuals genauso einfach falsch überschreiben wie öffentliche - sie definieren schließlich neue Klassen. Wenn die Öffentlichkeit eine bestimmte API nicht ändern sollte, machen Sie sie in öffentlich zugänglichen Objekten überhaupt nicht virtuell.

Zack Yezek
quelle