Wann sollte ich die private C ++ - Vererbung verwenden?

116

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?


quelle

Antworten:

60

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 finalgenau zu diesem Zweck ein kontextbezogenes Schlüsselwort bereitstellt :

class Sealed final // ...
David Rodríguez - Dribeas
quelle
Das ist eine großartige Technik. Ich werde einen Blogeintrag darüber schreiben.
1
Frage: Wenn wir keine virtuelle Vererbung verwenden würden, würde FailsToDerive kompilieren. Richtig?
4
+1. @Sasha: Eine korrekte virtuelle Vererbung ist erforderlich, da die am meisten abgeleitete Klasse die Konstruktoren aller virtuell vererbten Klassen immer direkt aufruft, was bei der einfachen Vererbung nicht der Fall ist.
j_random_hacker
5
Dies kann generisch gemacht werden, ohne einen benutzerdefinierten ClassSealer für jede Klasse zu erstellen, die Sie versiegeln möchten! Probieren Sie es aus: class ClassSealer {protected: ClassSealer () {}}; das ist alles.
+1 Iraimbilanja, sehr cool! Übrigens habe ich Ihren früheren Kommentar (jetzt gelöscht) zur Verwendung des CRTP gesehen: Ich denke, das sollte tatsächlich funktionieren, es ist nur schwierig, die Syntax für Vorlagenfreunde richtig zu machen. Aber auf jeden Fall ist Ihre Lösung ohne Vorlage viel besser :)
j_random_hacker
138

Ich benutze es die ganze Zeit. Ein paar Beispiele aus meinem Kopf:

  • Wenn ich einige, aber nicht alle Schnittstellen einer Basisklasse verfügbar machen möchte. Öffentliche Vererbung wäre eine Lüge, da die Substituierbarkeit von Liskov unterbrochen ist, während Komposition das Schreiben einer Reihe von Weiterleitungsfunktionen bedeuten würde.
  • Wenn ich von einer konkreten Klasse ohne virtuellen Destruktor ableiten möchte. Die öffentliche Vererbung würde Clients zum Löschen über einen Zeiger auf die Basis einladen und undefiniertes Verhalten aufrufen.

Ein typisches Beispiel ist die private Ableitung von einem STL-Container:

class MyVector : private vector<int>
{
public:
    // Using declarations expose the few functions my clients need 
    // without a load of forwarding functions. 
    using vector<int>::push_back;
    // etc...  
};
  • Bei der Implementierung des Adaptermusters erspart das private Erben von der angepassten Klasse die Weiterleitung an eine geschlossene Instanz.
  • So implementieren Sie eine private Schnittstelle Dies tritt häufig beim Beobachtermuster auf. Normalerweise abonniert sich meine Observer-Klasse, sagt MyClass, mit einem Betreff. Dann muss nur noch MyClass die Konvertierung MyClass -> Observer durchführen. Der Rest des Systems muss nichts darüber wissen, daher wird eine private Vererbung angezeigt.
Sprudler
quelle
4
@Krsna: Eigentlich glaube ich nicht. Hier gibt es nur einen Grund: Faulheit, abgesehen von der letzten, die schwieriger zu umgehen wäre.
Matthieu M.
11
Nicht so viel Faulheit (es sei denn, Sie meinen es gut). Dies ermöglicht die Erstellung neuer Überladungen von Funktionen, die ohne zusätzlichen Aufwand verfügbar gemacht wurden. Wenn sie in C ++ 1x 3 neue Überladungen hinzufügen push_back, erhalten MyVectorsie diese kostenlos.
David Stone
@ DavidStone, kannst du das nicht mit einer Vorlagenmethode machen?
Julien__
5
@ Julien__: Ja, du könntest schreiben 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 schreiben Base::f;. Wenn Sie den größten Teil der Funktionalität und Flexibilität wünschen, die Ihnen die private Vererbung und eine usingAnweisung bieten, haben Sie dieses Monster für jede Funktion (und vergessen Sie es nicht constund volatileüberladen Sie es!).
David Stone
2
Ich sage den größten Teil der Funktionalität, weil Sie immer noch einen zusätzlichen Verschiebungskonstruktor aufrufen, der in der using-Anweisungsversion nicht vorhanden ist. Im Allgemeinen würde man erwarten, dass dies weg optimiert wird, aber die Funktion könnte theoretisch einen nicht beweglichen Typ nach Wert zurückgeben. Die Weiterleitungsfunktionsvorlage verfügt außerdem über eine zusätzliche Vorlageninstanziierung und Steuertiefe. Dies kann dazu führen, dass Ihr Programm auf Implementierungsgrenzen stößt.
David Stone
31

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.

Harper Shelby
quelle
6
Erwähnenswert ist möglicherweise einer der Gründe, warum es in diesem Fall verwendet wird: Auf diese Weise kann die Optimierung der leeren Basisklasse durchgeführt werden. Dies ist nicht der Fall, wenn die Klasse ein Mitglied anstelle einer Basisklasse gewesen wäre.
Jalf
2
Seine Hauptanwendung besteht darin, den Speicherplatz dort zu reduzieren, wo es wirklich darauf ankommt, z. B. in richtliniengesteuerten Zeichenfolgenklassen oder in komprimierten Paaren. Tatsächlich verwendete boost :: compressed_pair eine geschützte Vererbung.
Johannes Schaub - litb
jalf: Hey, das habe ich nicht gemerkt. Ich dachte, nicht öffentliche Vererbung wurde hauptsächlich als Hack verwendet, wenn Sie Zugriff auf die geschützten Mitglieder einer Klasse benötigen. Ich frage mich, warum ein leeres Objekt bei der Verwendung von Komposition Platz beanspruchen würde. Wahrscheinlich für universelle Adressierbarkeit ...
3
Es ist auch praktisch, eine Klasse nicht kopierbar zu machen - einfach privat von einer leeren Klasse zu erben, die nicht kopierbar ist. Jetzt müssen Sie nicht mehr damit beschäftigt sein, einen Konstruktor und Zuweisungsoperator für private Kopien zu deklarieren, aber nicht zu definieren. Meyers spricht auch darüber.
Michael Burr
Ich wusste nicht, dass es bei dieser Frage tatsächlich um private Vererbung statt um geschützte Vererbung geht. Ja, ich denke, es gibt ziemlich viele Anwendungen dafür. Ich kann mir jedoch nicht viele Beispiele für geschützte Vererbung vorstellen: / sieht so aus, als wäre das nur selten nützlich.
Johannes Schaub - litb
23

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:

class FooInterface
{
public:
    virtual void DoSomething() = 0;
};

class FooUser
{
public:
    bool RegisterFooInterface(FooInterface* aInterface);
};

class FooImplementer : private FooInterface
{
public:
    explicit FooImplementer(FooUser& aUser)
    {
        aUser.RegisterFooInterface(this);
    }
private:
    virtual void DoSomething() { ... }
};

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.

Daemin
quelle
1
In der Tat ist private Vererbung private IS-A.
Neugieriger
18

Ich denke, der kritische Abschnitt aus dem C ++ FAQ Lite ist:

Eine legitime, langfristige Verwendung für die private Vererbung ist, wenn Sie eine Klasse Fred erstellen möchten, die Code in einer Klasse Wilma verwendet, und der Code aus der Klasse Wilma Mitgliedsfunktionen aus Ihrer neuen Klasse Fred aufrufen muss. In diesem Fall ruft Fred Nicht-Virtuals in Wilma auf, und Wilma ruft (normalerweise reine Virtuals) an sich auf, die von Fred überschrieben werden. Dies wäre mit der Komposition viel schwieriger zu tun.

Im Zweifelsfall sollten Sie die Komposition der privaten Vererbung vorziehen.

Bill die Eidechse
quelle
4

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

[...] Klasse Wilma muss Mitgliedsfunktionen Ihrer neuen Klasse Fred aufrufen.

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

privates Erbe ist nicht böse; Die Wartung ist nur teurer, da dadurch die Wahrscheinlichkeit erhöht wird, dass jemand etwas ändert, das Ihren Code beschädigt.

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).

vorspannen
quelle
Klingt nicht wirklich nützlich ... können Sie ein Beispiel
Ich denke, ich sehe, wohin Sie gehen ... Ein typischer Anwendungsfall könnte sein, dass Wilma eine Art Dienstprogrammklasse ist, die virtuelle Funktionen in Fred aufrufen muss, aber andere Klassen müssen nicht wissen, dass Fred in Bezug auf implementiert ist. von Wilma. Richtig?
j_random_hacker
Ja. Ich möchte darauf hinweisen, dass nach meinem Verständnis der Begriff "Schnittstelle" in Java häufiger verwendet wird. Als ich zum ersten Mal davon hörte, dachte ich, es hätte einen besseren Namen bekommen können. Da wir in diesem Beispiel eine Schnittstelle haben, mit der niemand so interagiert, wie wir es normalerweise von dem Wort halten.
Voreingenommenheit
@Noos: Ja, ich denke, Ihre Aussage "Wilma ist eine Schnittstelle" ist etwas mehrdeutig, da die meisten Leute dies so verstehen würden, dass Wilma eine Schnittstelle ist, die Fred der Welt liefern will , und nicht nur ein Vertrag mit Wilma.
j_random_hacker
@j_ Deshalb denke ich, dass Schnittstelle ein schlechter Name ist. Schnittstelle, die Laufzeit, muß nicht bedeuten , die Welt als würde man denken, sondern ist eine Garantie für Funktionalität. Eigentlich war ich über den Begriff Schnittstelle in meiner Program Design-Klasse umstritten. Aber wir verwenden, was uns gegeben wird ...
Voreingenommenheit
2

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.

class BigClass;

struct SomeCollection
{
    iterator begin();
    iterator end();
};

class BigClass : private SomeCollection
{
    friend struct SomeCollection;
    SomeCollection &GetThings() { return *this; }
};

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
BigClassIn diesem Beispiel ist keine Vorwärtsdeklaration erforderlich . Ich finde das interessant, aber es schreit hackisch in mein Gesicht.
Thomas Eding
2

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:

#ifdef __cplusplus
extern "C" {
#endif

    typedef struct
    {
        /* raw owning pointer, it's C after all */
        char const * name;

        /* more variables that need resources
         * ...
         */
    } Widget;

    Widget const * loadWidget();

    void freeWidget(Widget const * widget);

#ifdef __cplusplus
} // end of extern "C"
#endif

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:

Widget const * loadWidget()
{
    auto result = std::make_unique<Widget>();
    result->name = strdup("The Widget name");
    // More similar assignments here
    return result.release();
}

void freeWidget(Widget const * const widget)
{
    free(result->name);
    // More similar manual freeing of resources
    delete widget;
}

Es gibt jedoch mehrere Nachteile:

  • Manuelle Ressourcenverwaltung (z. B. Speicherverwaltung)
  • Es ist einfach, das einzurichten struct Falsche
  • Es ist leicht zu vergessen, die Ressourcen freizugeben, wenn die freigegeben werden struct
  • Es ist C-ish

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 Widgeteine Ressourcenverwaltungsinstanz zu erben und der abgeleiteten Klasse hinzuzufügen WidgetImpl:

class WidgetImpl : public Widget
{
public:
    // Added bonus, Widget's members get default initialized
    WidgetImpl()
        : Widget()
    {}

    void setName(std::string newName)
    {
        m_nameResource = std::move(newName);
        name = m_nameResource.c_str();
    }

    // More similar setters to follow

private:
    std::string m_nameResource;
};

Dies vereinfacht die Implementierung auf Folgendes:

Widget const * loadWidget()
{
    auto result = std::make_unique<WidgetImpl>();
    result->setName("The Widget name");
    // More similar setters here
    return result.release();
}

void freeWidget(Widget const * const widget)
{
    // No virtual destructor in the base class, thus static_cast must be used
    delete static_cast<WidgetImpl const *>(widget);
}

Auf diese Weise haben wir alle oben genannten Probleme behoben. Ein Kunde kann jedoch immer noch die Setter vergessen WidgetImplund den WidgetMitgliedern direkt zuweisen .

Private Erbschaft betritt die Bühne

Um die WidgetMitglieder zu kapseln, verwenden wir die private Vererbung. Leider benötigen wir jetzt zwei zusätzliche Funktionen, um zwischen beiden Klassen zu wechseln:

class WidgetImpl : private Widget
{
public:
    WidgetImpl()
        : Widget()
    {}

    void setName(std::string newName)
    {
        m_nameResource = std::move(newName);
        name = m_nameResource.c_str();
    }

    // More similar setters to follow

    Widget const * toWidget() const
    {
        return static_cast<Widget const *>(this);
    }

    static void deleteWidget(Widget const * const widget)
    {
        delete static_cast<WidgetImpl const *>(widget);
    }

private:
    std::string m_nameResource;
};

Dies macht folgende Anpassungen erforderlich:

Widget const * loadWidget()
{
    auto widgetImpl = std::make_unique<WidgetImpl>();
    widgetImpl->setName("The Widget name");
    // More similar setters here
    auto const result = widgetImpl->toWidget();
    widgetImpl.release();
    return result;
}

void freeWidget(Widget const * const widget)
{
    WidgetImpl::deleteWidget(widget);
}

Diese Lösung löst alle Probleme. Keine manuelle Speicherverwaltung und Widgetist 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 .

Matthäus Brandl
quelle
1

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.

sportswithin.com
quelle
1

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.

Bayda
quelle
0

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.

Rahul_cs12
quelle
0

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:

struct QuadraticEquationState {
   const double a;
   const double b;
   const double c;

   // named ctors so aggregate construction is available,
   // which is the default usage pattern
   // add your favourite ctors - throwing, try, cps
   static QuadraticEquationState read(std::istream& is);
   static std::optional<QuadraticEquationState> try_read(std::istream& is);

   template<typename Then, typename Else>
   static std::common_type<
             decltype(std::declval<Then>()(std::declval<QuadraticEquationState>()),
             decltype(std::declval<Else>()())>::type // this is just then(qes) or els(qes)
   if_read(std::istream& is, Then then, Else els);
};

// this works with QuadraticEquation as well by default
std::ostream& operator<<(std::ostream& os, const QuadraticEquationState& qes);

// no operator>> as we're const correct.
// we _might_ (not necessarily want) operator>> for optional<qes>
std::istream& operator>>(std::istream& is, std::optional<QuadraticEquationState>);

struct QuadraticEquationCache {
   mutable std::optional<double> determinant_cache;
   mutable std::optional<double> x1_cache;
   mutable std::optional<double> x2_cache;
   mutable std::optional<double> sum_of_x12_cache;
};

class QuadraticEquation : public QuadraticEquationState, // private if base is non-const
                          private QuadraticEquationCache {
public:
   QuadraticEquation(QuadraticEquationState); // in general, might throw
   QuadraticEquation(const double a, const double b, const double c);
   QuadraticEquation(const std::string& str);
   QuadraticEquation(const ExpressionTree& str); // might throw
}

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.

lorro
quelle
0

Wenn Sie eine std::ostreammit einigen kleinen Änderungen (wie in dieser Frage ) benötigen, müssen Sie möglicherweise

  1. Erstellen Sie eine Klasse, MyStreambufdie von abgeleitet iststd::streambuf Änderungen und implementieren Sie sie dort
  2. Erstellen Sie eine daraus abgeleitete Klasse, MyOStreamdie std::ostreamauch eine Instanz von initialisiert und verwaltet MyStreambufund den Zeiger auf diese Instanz an den Konstruktor von übergibtstd::ostream

Die erste Idee könnte darin bestehen, die MyStreamInstanz als Datenelement zur MyOStreamKlasse hinzuzufügen :

class MyOStream : public std::ostream
{
public:
    MyOStream()
        : std::basic_ostream{ &m_buf }
        , m_buf{}
    {}

private:
    MyStreambuf m_buf;
};

Basisklassen werden jedoch vor Datenelementen erstellt, sodass Sie einen Zeiger auf eine noch nicht erstellte std::streambufInstanz übergeben, für std::ostreamdie 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:

class MyOStream : public MyStreamBuf, public std::ostream
{
public:
    MyOStream()
        : MyStreamBuf{}
        , basic_ostream{ this }
    {}
};

Die resultierende Klasse kann jedoch auch als std::streambufInstanz verwendet werden, was normalerweise unerwünscht ist. Der Wechsel zur privaten Vererbung löst dieses Problem:

class MyOStream : private MyStreamBuf, public std::ostream
{
public:
    MyOStream()
        : MyStreamBuf{}
        , basic_ostream{ this }
    {}
};
Matthäus Brandl
quelle
-1

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.

hasen
quelle
bist du sarkastisch? Alles was ich habe ist ein -1! sowieso werde ich dies nicht löschen, auch wenn es -100 Stimmen bekommt
hasen
9
" Sie verletzen im Grunde die Kapselung " Können Sie ein Beispiel geben?
Neugieriger
1
Daten in einer Klasse und Verhalten in einer anderen klingen nach einer Erhöhung der Flexibilität, da es mehr als eine Verhaltensklasse und Clients geben kann und Sie auswählen können, welche sie benötigen, um das zu erfüllen, was sie wollen
makar