Bietet Private-Vererbung irgendwelche Vorteile gegenüber Komposition?



  • zwei weitere Punkte, entnommen aus Sutter/Alexandrescu's C++ Coding Standards:
    benutze Vererbung, wenn:
    - das benutze Objekt vor einer anderen Basisklasse konstruiert oder nach ihr zwerstört werden muss
    - es im Zusammenhang mit virtuellen Basisklassen nötig ist.



  • Was haltet ihr eigentlich davon, privat abzuleiten und einzelne Funktionen mittels using öffentlich zu machen? So verhindert man, dass das Objekt als Basisklasseninstanz angesprochen wird, kann aber andere Vorteile der Vererbung nutzen.

    Selbst habe ich das noch nie so gemacht, allerdings auch schon bei fremden Code gesehen.



  • Nexus schrieb:

    Was haltet ihr eigentlich davon, privat abzuleiten und einzelne Funktionen mittels using öffentlich zu machen? So verhindert man, dass das Objekt als Basisklasseninstanz angesprochen wird, kann aber andere Vorteile der Vererbung nutzen.

    Meistens dürfte gelten: Dazu müßte eigentlich gelten Derived IST EIN Base. Anderenfalls müßte man immer stets ununterbrpchen aufpassen, ob das geusingte noch gültig ist. Und wenn man eh IST EIN hat, kann man auch gleich öffentlich erben.



  • Kann es nicht sinnvoll sein, auf diese Weise Codeduplizierung zu vermeiden? Oder hältst du eine Aggregation immer für angebrachter?

    Ein ausgedachtes Beispiel: Zwei Klassen in einem GUI-Framework, Panel und Window . Panel ist eine Unterteilung des Fensters und kann selber beliebige GUI-Komponenten (auch andere Panel s) aufnehmen. Window soll das auch können, aber es muss zuoberst stehen und darf nicht als Bestandteil anderer Komponenten fungieren (darf also nicht als Panel behandelt werden). Deshalb könnte es privat von Panel erben und die gemeinsamen Funktionen öffentlich machen.

    Hmm... Ganz sauber kommt mir das selber nicht vor, zumal hier gegen Aggregation nicht wirklich etwas spricht. 😉

    Ich wäre trotzdem froh um Meinungen dazu, mir fällt leider kein besseres Beispiel ein. Wenn die Beschreibung etwas ungenau war, kann ich das Beispiel gerne an einem Stück Code illustrieren.



  • Hmm. Ich würde das wahrscheinlich eher so lösen, dass ich eine Klasse Container definiere und dann kannst du da Window und Panel davon ableiten, da beide als Container fungieren und somit Container sind. (Das ist ja deine grundsätzliche Aussage oben das Window und Panel Container sind).



  • Man kann damit Designschwächen anderer Bibliotheken ausbügeln:

    Nämlich immer dann, wenn man eine Aggregation will, aber protected member des Objektes braucht, dann ist die private Vererbung das Einzige, was einen rettet.



  • otze schrieb:

    Man kann damit Designschwächen anderer Bibliotheken ausbügeln

    Jup. Wohl die Hauptanwendung.



  • drakon schrieb:

    Hmm. Ich würde das wahrscheinlich eher so lösen, dass ich eine Klasse Container definiere und dann kannst du da Window und Panel davon ableiten, da beide als Container fungieren und somit Container sind. (Das ist ja deine grundsätzliche Aussage oben das Window und Panel Container sind).

    Panel würde aber von einer zusätzlichen (abstrakten) Basisklasse für alle GUI-Komponenten erben, sagen wir Component . Elementare Komponenten (nennen wir sie Widgets) wie Schaltflächen werden ebenfalls von dieser Klasse abgeleitet.

    Wäre hier Mehrfachvererbung angebracht?

    +-----------+       +-----------+
           | Container |       | Component |
           +-----------+       +-----------+
                 ^                   ^
                 |                   |
          +------+-------+   +-------+-------+
          |              |   |               |
    +-----------+    +-----------+     +-----------+
    |  Window   |    |   Panel   |     |   Widget  |
    +-----------+    +-----------+     +-----------+
    


  • Ja, Mehrfachvererbung empfinde ich durchaus als einen guten Lösungsansatz. (Potentiell gibt es sogar noch mehr, von denen du erben kannst, wenn man wirklich eine GUI Library erstellt).



  • drakon schrieb:

    Potentiell gibt es sogar noch mehr, von denen du erben kannst, wenn man wirklich eine GUI Library erstellt.

    Ich will nicht eine komplette GUI schreiben, eher was Kleines für SFML, vor allem für alltägliche Dinge. Erweitern kann ich es immer noch. Gerade auch deshalb habe ich mir vorgenommen, von Anfang an auf schönes Design zu achten.

    Klar könnte man noch mehr machen. Ich versuche aber, allzu tiefe Klassenhierarchien zu vermeiden und die Zusammenhänge - sofern es geht - einfach zu halten. Im Gegensatz zu vielen GUIs in C++ möchte ich dem Anwender auch die Möglichkeit geben, die Komponenten selbst zu verwalten. Also vorerst nichts mit Pseudo-Garbage-Collector. 😉

    Das mit dem Container muss ich mir noch überlegen. So eine abstrakte Basisklasse macht vor allem Sinn, wenn man verschiedene abgeleitete Klassen gemeinsam ansprechen will, dafür fehlen mir gerade Anwendungsfälle. Was ich mir auch überlege, ist eine Aggregation, also dass das Fenster ein Standard-Panel besitzt (das eben das ganze Fenster ausfüllen würde). Um an diesem Änderungen vorzunehmen, könnte ich entweder Methoden wrappen, eine Referenz auf das interne Panel zurückgeben (nicht besonders sauber) oder eben private Vererbung benutzen. Letzteres scheint mir aber wie gesagt ebenfalls nicht ganz das Wahre zu sein...

    Oder ich lasse den Benutzer das Panel selbst erstellen und verweise im Fenster lediglich darauf. Somit ist er flexibler. Das ist allerdings mühsam, weil man zu jedem Fenster gleich ein Panel erstellen muss, um es überhaupt benutzen zu können.



  • Die Sache mit der privaten Vererbung, um dann einfach per using die geerbten Methoden zu veröffentlichen ist natürlich sehr verlockend, weil man dann nicht die ganzen Methoden nach Schema F definieren und an das Member durchreichen muss. Die Semantik sit wie volkard gesagt hat aber eben eine ganz andere.
    Für sowas würde ich mir ein neues Sprachelement wünschen:

    class A
    {
      int foo();
      char foo(int i);
      string bar(int a, bool b, char c);
    };
    
    //böse:
    class B : private A
    {
    public:
      using A::foo;
      using A::bar;
    };
    
    //richtig, aber aufwändig und langatmig:
    class C
    {
      A theA;
    public:
      int foo() { return theA.foo(); }
      char foo(int i) { return theA.foo(i); }
      string bar(int a, bool b, char c) { return theA.bar(a,b,c); }
    };
    
    //schön wärs:
    class D
    {
      A theA;
    public:
      expose theA.foo;
      expose theA.bar;
    };
    


  • Ich finde es nicht nachvollziehbar, sich im Falle privater Vererbung mit der Definition "IST-EIN" aufzuhalten und dann von diesem Standpunkt aus private Vererbung zu beurteilen.

    Im Falle öffentlicher Vererbung halte ich IST-EIN für ein brauchbares Kriterium, bei privater Vererbung aber ist es Unsinn. Wie wäre es stattdessen mit IST-IMPLEMENTIERT-MIT (oder ähnlichem)? Dann wäre auch an Nexus' Vorschlag der Übernahme von Funktionen privater Oberklassen per using nur noch wenig auszusetzen.

    @pumuckl

    Der Sinn von expose erschließt sich mir nicht. Was ist der Vorteil gegenüber der using-Lösung? Abgesehen halt vom IST-EIN-"Problem".

    Stefan.



  • DStefan schrieb:

    Ich finde es nicht nachvollziehbar, sich im Falle privater Vererbung mit der Definition "IST-EIN" aufzuhalten und dann von diesem Standpunkt aus private Vererbung zu beurteilen.
    Im Falle öffentlicher Vererbung halte ich IST-EIN für ein brauchbares Kriterium, bei privater Vererbung aber ist es Unsinn. Wie wäre es stattdessen mit IST-IMPLEMENTIERT-MIT (oder ähnlichem)? Dann wäre auch an Nexus' Vorschlag der Übernahme von Funktionen privater Oberklassen per using nur noch wenig auszusetzen.

    der main() kannst du sowas meinetwegen erzählen. aber glaubt die erbende klasse das denn auch? klares nein.
    und wozu erben, wenn's nicht not tut? das übt doch nur unnötig druck auf die klassenhierarchie (taxonomie!) aus und führt dann zu mehrfachen erblichkeitsproblemen.



  • Ich hab letztens private Vererbung hierfür eingesetzt:

    class ENTITYSYSTEM_API EntityManager : private MessageClient { ... };
    
    class ENTITYSYSTEM_API MessageClient : boost::noncopyable {
    
    protected:
    	// Subscribes to receive messages of a specific type.
    	void Subscribe(const MessageType&);
    
    	// Unsubscribes to receive messages of a specific type.
    	void Unsubscribe(const MessageType&);
    
    	// Processes a received message. It is guaranteed that no message is received in which this
    	// message client is not interested.
    	virtual void ProcessMessage(const Message&) = nullptr;
    
    };
    

    Die Idee hier ist, dass der EntityManager am Message-System teilnehmen möchte, hierzu muss er eine virtuelle Methode overriden. Es ist aber keinesfalls gewünscht, dass jemand von außen den EntityManager für irgendwas subscriben kann, sondern der EntityManager weiß selber genau, was für Messages er braucht.

    Daher fällt öffentliche Vererbung aus. Komposition wäre umständlich, ich müsste erst von MessageClient ableiten und eine Instanz dieser konkreten Klasse als member aufnehmen. Daher finde ich das hier eine brauchbare Lösung (andere Meinungen mit Begründung sind willkommen).

    Trotzdem zieht man im Allgemeinen Komposition natürlich vor. Schlauerweise nimmt man immer die schwächste Verbindung, die noch sinnvoll ist, also Aggregation, Komposition, Vererbung und nur zuletzt friend.



  • Optimizer schrieb:

    virtual void ProcessMessage(const Message&) = nullptr;
    

    Da ist auch nullptr erlaubt?



  • Optimizer schrieb:

    Ich hab letztens private Vererbung hierfür eingesetzt:...

    Alternativ könnte man überlegen, nicht nur Empfängerobjekte zu registrieren, die je nach Nachricht eine bestimmte virtuelle Funktion anbieten müssen, sondern beliebige Objekte/Methodenzeiger-Paare registrieren zu können.



  • DStefan schrieb:

    Ich finde es nicht nachvollziehbar, sich im Falle privater Vererbung mit der Definition "IST-EIN" aufzuhalten und dann von diesem Standpunkt aus private Vererbung zu beurteilen.

    Im Falle öffentlicher Vererbung halte ich IST-EIN für ein brauchbares Kriterium, bei privater Vererbung aber ist es Unsinn. Wie wäre es stattdessen mit IST-IMPLEMENTIERT-MIT (oder ähnlichem)?

    Dann schon "IST-IMPLEMENTIERT-MIT-UND-FÜR-SICH-SELBST-UND-IM-KREISE-DER-FREUNDE-IST-EIN" - für sich selbst und für alle friends gilt nunmal die Ist-Ein-Beziehung. Und die wegzudiskutieren hilft auch nicht dagegen dass es semantisch eben etwas anderes ist als Aggregation.

    DStefan schrieb:

    @pumuckl

    Der Sinn von expose erschließt sich mir nicht. Was ist der Vorteil gegenüber der using-Lösung? Abgesehen halt vom IST-EIN-"Problem".

    Eben. Abgesehn von der Typsicherheit kann man auch Makros an Stelle vieler Templates benutzen. Man kann für viele Schwächen der Sprache Workarounds finden, die super funktionieren auch wenn sie eigentlich was anderes bedeuten.

    btw:

    class E
    {
      A someA;
      A anotherA;
    public:
      expose someA.foo;
      expose anotherA.bar;
    };
    

    nicht machbar mit Vererbung 😛



  • volkard schrieb:

    Optimizer schrieb:

    virtual void ProcessMessage(const Message&) = nullptr;
    

    Da ist auch nullptr erlaubt?

    Ich hoffe es. Im Moment ist es nur ein #define.



  • Optimizer schrieb:

    Ich hoffe es.

    Du hast =0 gelernt, =0 steht im Standard, andere haben =0 gelernt.
    Wozu abweichen? Purer Flagellantismus?

    Ganz verwundert bin über das rätselhafte =nullptr (oder =0x0). Wenn schon, dann mach

    #define abstract =0
    

    dann hätte das noch ne lesbare Aussage.

    noch besser:

    virtual void ProcessMessage(const Message&) = 0; //abstract
    


  • Ich habe bisher immer so gedacht, = 0 wäre gedanklich der Weg, um nen Funktionspointer (was anderes ist der Name der Funktion ja nicht) ungültig sein zu lassen. Bisher war = 0 auch der Weg, um nullptr jeglicher Art zu erhalten.

    Anscheinend ist aber auch weiterhin nur = 0 zulässig was ich etwas inkonsequent finde. Mit einem Integer eine nicht implementierte Funktion zu beschreiben, erscheint mir nicht logisch.


Anmelden zum Antworten