Bietet Private-Vererbung irgendwelche Vorteile gegenüber Komposition?



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



  • volkard schrieb:

    noch besser:

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

    Der Trivialkommentar ist nicht besser, ich schreibe keine Kommentare für Leute, die C++ nicht grundsätzlich lesen können. Das ist für mich auf der selben Ebene wie:

    int a = b + c;  // here, we add b and c
    


  • Optimizer schrieb:

    Mit einem Integer eine nicht implementierte Funktion zu beschreiben, erscheint mir nicht logisch.

    Ja, man wollte halt nicht noch ein Schlüsselwort einführen, was ein wenig schade ist. Dieses =0 mit abstrakten Funktionen und abstrakten Basisklassen ist schon ein gewaltiger Brocken und hat mehr Zuwendung verdient.



  • In Hinsicht auf den neuen Standard würde ich -- wenn ich mich von 0 abwende -- wohl eher sowas machen:

    #define abstract 0
    

    Dann bleibt man einheitlich gegenüber verbotenen und standardgenerierten Funktionen:

    class MyClass
    {
        virtual void Foo()                      = abstract;
                     MyClass()                  = default;
        MyClass&     operator= (const MyClass&) = delete;
    };
    

    Obwohl ich das Schlüsselwort-Recycling ziemlich schlimm finde, vor allem bei delete . Meines Erachtens sind mehr Schlüsselwörter besser als mehrdeutige, vor allem wenn die Anwendungsbereiche sich so stark wie hier unterscheiden (bei static könnte man sich darüber streiten...).



  • <offtopic>

    hast du nen link, wo beschrieben ist, was =delete oder =default bedeutet? oO
    is iwie doof zu ergooglen - zumindest war ich zu unvermögend dazu ;o)





  • unskilled schrieb:

    hast du nen link, wo beschrieben ist, was =delete oder =default bedeutet? oO
    is iwie doof zu ergooglen - zumindest war ich zu unvermögend dazu ;o)

    Wie wärs mit einem Artikel aus dem C++-Magazin? Unter Punkt 2.7 findest du die beschriebenen Sprachmittel.



  • Nexus schrieb:

    unskilled schrieb:

    hast du nen link, wo beschrieben ist, was =delete oder =default bedeutet? oO
    is iwie doof zu ergooglen - zumindest war ich zu unvermögend dazu ;o)

    Wie wärs mit einem Artikel aus dem C++-Magazin? Unter Punkt 2.7 findest du die beschriebenen Sprachmittel.

    oh - danke - den artikel hab ich iwie noch nie gesehen, obwohl ich eigtl regelmäßig nach artikeln gesucht habe...



  • Nexus schrieb:

    Obwohl ich das Schlüsselwort-Recycling ziemlich schlimm finde, vor allem bei delete . Meines Erachtens sind mehr Schlüsselwörter besser als mehrdeutige, vor allem wenn die Anwendungsbereiche sich so stark wie hier unterscheiden (bei static könnte man sich darüber streiten...).

    Ich finde es in dem Fall gar nicht mal so schlimm, weil ja kaum Verwechslungsgefahr besteht, da sie eben genau so unterschiedliche Anwendungsgebiete haben, aber das Schlüsselwort wirklich sehr genau aussagt, was passiert. Ich fände es verwirrender, wenn da neue, nicht ganz so passende Schlüsselwörter benutzt würden.

    Natürlich macht es ein paar Sachen, wie z.B das Suchen nach der Funktionsweise (wie unskilled bereits schön gezeigt hat) einiges komplizierter, da es mehrdeutig ist. Auch können so Fragen, wie "was macht delete" nicht mehr so einfach beantwortet werden.

    Aber ich denke, dass wenn man sich daran gewöhnt hat das einiges intuitiver zu benutzen ist, als wenn man ein Haufen Keywords mehr hat.



  • Ja, ist wohl alles ein wenig Gewohnheitssache. Ich finde einfach, delete gehört zur Speicherverwaltung und hat in Memberfunktionsattributen nichts zu suchen. So genau wie du sagst passt es auch gar nicht: "to delete" bedeutet löschen. Vom Löschen einer Funktion zu reden halte ich nicht für besonders intuitiv. Meiner Ansicht nach wäre sowas wie forbidden oder ähnlich treffender gewesen.

    Und wieso glaubst du, dass es hier komplizierter wäre, ein neues Schlüsselwort zu benutzen? Dann hätte man eine klare Trennung und würde nicht krampfhaft versuchen, das Freigeben von Speicherbereich mit dem Weglassen von Methoden zu assoziieren.

    Anderes Beispiel fürs Keyword-Recycling: enum class . Was hat denn bitte eine Enumeration mit einer Klasse zu tun? enum struct hielte ich für besser, Struktur kann man noch allgemeiner interpretieren. Aber auch das ist nicht wirklich das Wahre.



  • Komplizierter meinte ich, dass man nicht krampfhat einen anderen Namen für etwas sucht, wenn der passende reicht. Sprich man sucht sich dann ein sehr ähnliches Wort, dass vielleicht in anderen Sprachen (z.B Deutsch) gar keinen Unterschied macht. Dann wäre es in dem Sinne komplizierter, dass man teils vor einer "Auswahl" steht, wo man dann jedes mal in die Falle tritt und das falsche Keyword nimmt. 😉
    (z.B erinnerst du dich bestimmt an das Beispiel von letzens mit count/size. genau solche Sachen meine ich).


Anmelden zum Antworten