Im Gegensatz zur geschützten Vererbung fand die private C ++ - Vererbung Eingang in die Mainstream-C ++ - Entwicklung. Ich habe jedoch immer noch keine gute Verwendung dafür gefunden.
Wann benutzt ihr es?
Hinweis nach Annahme der Antwort: Dies ist KEINE vollständige Antwort. Lesen Sie andere Antworten wie hier (konzeptionell) und hier (sowohl theoretisch als auch praktisch), wenn Sie an der Frage interessiert sind. Dies ist nur ein ausgefallener Trick, der mit privater Vererbung erreicht werden kann. Es ist zwar schick , aber nicht die Antwort auf die Frage.
Neben der grundlegenden Verwendung der privaten Vererbung, die in den häufig gestellten Fragen zu C ++ (in den Kommentaren anderer verknüpft) aufgeführt ist, können Sie eine Kombination aus privater und virtueller Vererbung verwenden, um eine Klasse zu versiegeln (in der .NET-Terminologie) oder eine Klasse endgültig zu machen (in der Java-Terminologie). . Dies ist keine übliche Verwendung, aber ich fand es trotzdem interessant:
class ClassSealer {
private:
friend class Sealed;
ClassSealer() {}
};
class Sealed : private virtual ClassSealer
{
// ...
};
class FailsToDerive : public Sealed
{
// Cannot be instantiated
};
Versiegelt kann instanziiert werden. Es stammt von ClassSealer und kann den privaten Konstruktor direkt aufrufen, da er ein Freund ist.
FailsToDerive wird nicht kompiliert, da es den ClassSealer- Konstruktor direkt aufrufen muss (virtuelle Vererbungsanforderung), kann dies jedoch nicht, da es in der Sealed- Klasse privat ist und in diesem Fall FailsToDerive kein Freund von ClassSealer ist .
BEARBEITEN
In den Kommentaren wurde erwähnt, dass dies zu diesem Zeitpunkt mit CRTP nicht generisch gemacht werden konnte. Der C ++ 11-Standard hebt diese Einschränkung auf, indem eine andere Syntax bereitgestellt wird, um sich mit Vorlagenargumenten anzufreunden:
template <typename T>
class Seal {
friend T; // not: friend class T!!!
Seal() {}
};
class Sealed : private virtual Seal<Sealed> // ...
Dies ist natürlich alles umstritten, da C ++ 11 final
genau zu diesem Zweck ein kontextbezogenes Schlüsselwort bereitstellt :
class Sealed final // ...
Ich benutze es die ganze Zeit. Ein paar Beispiele aus meinem Kopf:
Ein typisches Beispiel ist die private Ableitung von einem STL-Container:
quelle
push_back
, erhaltenMyVector
sie diese kostenlos.template<typename... Args> constexpr decltype(auto) f(Args && ... args) noexcept(noexcept(std::declval<Base &>().f(std::forward<Args>(args)...)) and std::is_nothrow_move_constructible<decltype(std::declval<Base &>().f(std::forward<Args>(args)...))>) { return m_base.f(std::forward<Args>(args)...); }
oder du könntest mit schreibenBase::f;
. Wenn Sie den größten Teil der Funktionalität und Flexibilität wünschen, die Ihnen die private Vererbung und eineusing
Anweisung bieten, haben Sie dieses Monster für jede Funktion (und vergessen Sie es nichtconst
undvolatile
überladen Sie es!).Die kanonische Verwendung der privaten Vererbung ist die Beziehung "implementiert in Bezug auf" (dank Scott Meyers '' Effective C ++ 'für diesen Wortlaut). Mit anderen Worten, die externe Schnittstelle der ererbenden Klasse hat keine (sichtbare) Beziehung zur geerbten Klasse, sondern verwendet sie intern, um ihre Funktionalität zu implementieren.
quelle
Eine nützliche Verwendung der privaten Vererbung besteht darin, dass Sie eine Klasse haben, die eine Schnittstelle implementiert, die dann bei einem anderen Objekt registriert wird. Sie machen diese Schnittstelle privat, sodass sich die Klasse selbst registrieren muss und nur das spezifische Objekt, bei dem sie registriert ist, diese Funktionen verwenden kann.
Beispielsweise:
Daher kann die FooUser-Klasse die privaten Methoden von FooImplementer über die FooInterface-Schnittstelle aufrufen, während andere externe Klassen dies nicht können. Dies ist ein großartiges Muster für die Behandlung bestimmter Rückrufe, die als Schnittstellen definiert sind.
quelle
Ich denke, der kritische Abschnitt aus dem C ++ FAQ Lite ist:
Im Zweifelsfall sollten Sie die Komposition der privaten Vererbung vorziehen.
quelle
Ich finde es nützlich für Schnittstellen (dh abstrakte Klassen), die ich erbe, wenn ich nicht möchte, dass anderer Code die Schnittstelle berührt (nur die erbende Klasse).
[in einem Beispiel bearbeitet]
Nehmen Sie das oben verlinkte Beispiel . Sagt, dass
Das heißt, Wilma verlangt von Fred, dass er bestimmte Mitgliedsfunktionen aufrufen kann, oder vielmehr, dass Wilma eine Schnittstelle ist . Daher, wie im Beispiel erwähnt
Kommentare zum gewünschten Effekt von Programmierern, die unsere Schnittstellenanforderungen erfüllen oder den Code brechen müssen. Und da fredCallsWilma () geschützt ist, können nur Freunde und abgeleitete Klassen es berühren, dh eine geerbte Schnittstelle (abstrakte Klasse), die nur die erbende Klasse berühren kann (und Freunde).
[in einem anderen Beispiel bearbeitet]
Diese Seite beschreibt kurz private Schnittstellen (aus einem anderen Blickwinkel).
quelle
Manchmal finde ich es nützlich, die private Vererbung zu verwenden, wenn ich eine kleinere Schnittstelle (z. B. eine Sammlung) in der Schnittstelle einer anderen verfügbar machen möchte, wobei für die Implementierung der Sammlung der Zugriff auf den Status der verfügbar machenden Klasse erforderlich ist, ähnlich wie bei inneren Klassen in Java.
Wenn SomeCollection dann auf BigClass zugreifen muss, ist dies möglich
static_cast<BigClass *>(this)
. Es ist nicht erforderlich, dass ein zusätzliches Datenelement Speicherplatz beansprucht.quelle
BigClass
In diesem Beispiel ist keine Vorwärtsdeklaration erforderlich . Ich finde das interessant, aber es schreit hackisch in mein Gesicht.Ich habe eine nette Anwendung für die private Vererbung gefunden, obwohl sie nur begrenzt verwendet werden kann.
Problem zu lösen
Angenommen, Sie erhalten die folgende C-API:
Jetzt müssen Sie diese API mit C ++ implementieren.
C-ish Ansatz
Natürlich könnten wir einen C-ischen Implementierungsstil wie folgt wählen:
Es gibt jedoch mehrere Nachteile:
struct
Falschestruct
C ++ Ansatz
Wir dürfen C ++ verwenden. Warum also nicht die volle Leistung nutzen?
Einführung des automatisierten Ressourcenmanagements
Die oben genannten Probleme hängen im Wesentlichen alle mit der manuellen Ressourcenverwaltung zusammen. Die Lösung besteht darin, für jede Variable
Widget
eine Ressourcenverwaltungsinstanz zu erben und der abgeleiteten Klasse hinzuzufügenWidgetImpl
:Dies vereinfacht die Implementierung auf Folgendes:
Auf diese Weise haben wir alle oben genannten Probleme behoben. Ein Kunde kann jedoch immer noch die Setter vergessen
WidgetImpl
und denWidget
Mitgliedern direkt zuweisen .Private Erbschaft betritt die Bühne
Um die
Widget
Mitglieder zu kapseln, verwenden wir die private Vererbung. Leider benötigen wir jetzt zwei zusätzliche Funktionen, um zwischen beiden Klassen zu wechseln:Dies macht folgende Anpassungen erforderlich:
Diese Lösung löst alle Probleme. Keine manuelle Speicherverwaltung und
Widget
ist damit schön gekapseltWidgetImpl
keine öffentlichen Datenelemente mehr vorhanden sind. Es macht die Implementierung einfach korrekt und schwer (unmöglich?) Falsch zu verwenden.Die Codefragmente bilden ein Kompilierungsbeispiel für Coliru .
quelle
Wenn abgeleitete Klasse - Code wiederverwenden muss und - Sie können die Basisklasse nicht ändern und - ihre Methoden mithilfe der Mitglieder der Basis unter einer Sperre schützen.
Dann sollten Sie die private Vererbung verwenden, da sonst die Gefahr besteht, dass entsperrte Basismethoden über diese abgeleitete Klasse exportiert werden.
quelle
Manchmal kann dies eine Alternative zur Aggregation sein , z. B. wenn Sie eine Aggregation wünschen, aber das Verhalten einer aggregierbaren Entität geändert haben (Überschreiben der virtuellen Funktionen).
Aber Sie haben Recht, es gibt nicht viele Beispiele aus der realen Welt.
quelle
Private Vererbung, die verwendet wird, wenn die Beziehung nicht "ist eine" ist, sondern eine neue Klasse "in Bezug auf eine vorhandene Klasse implementiert" werden kann oder eine neue Klasse "wie eine vorhandene Klasse" funktioniert.
Beispiel aus "C ++ - Codierungsstandards von Andrei Alexandrescu, Herb Sutter": - Beachten Sie, dass zwei Klassen Square und Rectangle jeweils virtuelle Funktionen zum Einstellen ihrer Höhe und Breite haben. Dann kann Square nicht korrekt von Rectangle erben, da Code, der ein modifizierbares Rectangle verwendet, davon ausgeht, dass SetWidth die Höhe nicht ändert (unabhängig davon, ob Rectangle diesen Vertrag explizit dokumentiert oder nicht), während Square :: SetWidth diesen Vertrag und seine eigene Rechteckigkeit nicht beibehalten kann die selbe Zeit. Aber Rectangle kann auch nicht korrekt von Square erben, wenn Clients von Square beispielsweise annehmen, dass die Fläche eines Quadrats seine quadratische Breite ist, oder wenn sie sich auf eine andere Eigenschaft verlassen, die für Rectangles nicht gilt.
Ein Quadrat "ist-ein" Rechteck (mathematisch), aber ein Quadrat ist kein Rechteck (verhaltensmäßig). Folglich sagen wir anstelle von "is-a" lieber "works-like-a" (oder, wenn Sie es vorziehen, "us-as-a"), um die Beschreibung weniger anfällig für Missverständnisse zu machen.
quelle
Eine Klasse enthält eine Invariante. Die Invariante wird vom Konstruktor festgelegt. In vielen Situationen ist es jedoch hilfreich, den Darstellungsstatus des Objekts anzuzeigen (den Sie über das Netzwerk übertragen oder in einer Datei speichern können - DTO, wenn Sie dies bevorzugen). REST wird am besten in Bezug auf einen AggregateType durchgeführt. Dies gilt insbesondere dann, wenn Sie const korrekt sind. Erwägen:
Zu diesem Zeitpunkt können Sie Cachesammlungen einfach in Containern speichern und bei der Erstellung nachschlagen. Praktisch, wenn es eine echte Verarbeitung gibt. Beachten Sie, dass der Cache Teil der QE ist: In der QE definierte Operationen können bedeuten, dass der Cache teilweise wiederverwendbar ist (z. B. hat c keinen Einfluss auf die Summe). Wenn es jedoch keinen Cache gibt, lohnt es sich, ihn nachzuschlagen.
Private Vererbung kann fast immer von einem Mitglied modelliert werden (bei Bedarf wird auf die Basis verwiesen). Es lohnt sich einfach nicht immer, so zu modellieren. Manchmal ist die Vererbung die effizienteste Darstellung.
quelle
Wenn Sie eine
std::ostream
mit einigen kleinen Änderungen (wie in dieser Frage ) benötigen, müssen Sie möglicherweiseMyStreambuf
die von abgeleitet iststd::streambuf
Änderungen und implementieren Sie sie dortMyOStream
diestd::ostream
auch eine Instanz von initialisiert und verwaltetMyStreambuf
und den Zeiger auf diese Instanz an den Konstruktor von übergibtstd::ostream
Die erste Idee könnte darin bestehen, die
MyStream
Instanz als Datenelement zurMyOStream
Klasse hinzuzufügen :Basisklassen werden jedoch vor Datenelementen erstellt, sodass Sie einen Zeiger auf eine noch nicht erstellte
std::streambuf
Instanz übergeben, fürstd::ostream
die ein undefiniertes Verhalten vorliegt.Die Lösung wird in Bens Antwort auf die oben genannte Frage vorgeschlagen . Erben Sie einfach zuerst vom Stream-Puffer, dann vom Stream und initialisieren Sie den Stream dann mit
this
:Die resultierende Klasse kann jedoch auch als
std::streambuf
Instanz verwendet werden, was normalerweise unerwünscht ist. Der Wechsel zur privaten Vererbung löst dieses Problem:quelle
Nur weil C ++ eine Funktion hat, heißt das nicht, dass sie nützlich ist oder verwendet werden sollte.
Ich würde sagen, du solltest es überhaupt nicht benutzen.
Wenn Sie es trotzdem verwenden, verletzen Sie im Grunde die Kapselung und verringern den Zusammenhalt. Sie ordnen Daten einer Klasse zu und fügen Methoden hinzu, mit denen die Daten in einer anderen Klasse bearbeitet werden.
Wie andere C ++ - Funktionen kann es verwendet werden, um Nebenwirkungen wie das Versiegeln einer Klasse zu erzielen (wie in der Antwort von dribeas erwähnt), aber dies macht es nicht zu einer guten Funktion.
quelle