„is-implemented-in-terms-of“: Komposition vs. Vererbung



  • Hallo,

    soweit ich das sehe, gibt es zwei grundsätzliche Möglichkeiten, IIITO zu implementieren: Via Komposition und via private (oder öffentliche, die ich außer Acht lasse) Vererbung.

    GOTW #60 argumentiert, dass Komposition den Vorteil hat, eine Ausnahme-sichere Implementierung von op= zu vereinfachen.

    Nur: Wo sind die Vorteile der privaten Vererbung? Oder: wieso benutzt man nicht *immer* Komposition und vergisst private Vererbung? Gibt es Unterschiede in der Performance?

    Letztendlich läuft es bei mir darauf hinaus, dass ich ein Klassentemplate A mittels Klassentemplate B implementiere. Zufällig haben diese beiden Klassen aber eine *identische* (!) Interface. Am liebsten würde ich also folgendes schreiben:

    template <typename T> using A = detail::B<T>;
    

    Da das noch nicht funktioniert, wähle ich halt den Umweg über IIITO. Klasse B hat übrigens einen Ausnahmen-sicheren op= (ich benutzte die Implementierung via 'swap' die in „Effective C++“ vorgestellt ist). D.h. das Argument aus GOTW #60 trifft bei mir nicht zu. Soll ich trotzdem Komposition verwenden?



  • Ich glaube Herb Sutter war es in Exceptional C++ Style, der folgendes Beispiel angebracht hat, wo es ohne Vererbung nicht geht.

    /* Eine Klasse habe folgendes Interface */
    class Timer
    {
    protected:
        void Start(int ms);
    
        virtual void OnTick() = 0;
    };
    

    Bei einer Klasse, die diesen Timer wiederverwenden möchte (die in-terms-of Timer implementiert ist) geht es nicht mit Komposition.



  • Hallo LJ,

    stimmt, in Deinem Beispiel ist das etwas anderes. Gut, dann werde ich halt Komposition verwenden. Die Performance sollte ja eigentlich wirklich identisch sein.



  • Ich such's heute abend mal exakt raus, aber ich meine er schreibt sinngemäß, dass die Notwendigkeit der Implementierung einer abstrakten Methode für ihn der einzige Grund wäre, keine Komposition zu verwenden.



  • Gut, dass du oeffentliche Vererbung aussen vorlaesst, denn das waere kein IIITO mehr, sondern Is-A, und damit was anderes.
    Wie du schon schreibst, wird IIITO im Allgemeinen mit Komposition geregelt, die einzigen Ausnahmen sind Situationen, in denen die Komposition nicht ausreicht. Das ist
    Situation a): Du benoetigst Zugriff auf protected member, den du nur durch Vererbung bekommst.
    Situation b): Du willst virtuelle Methoden ueberladen

    Wobei du Situation b) meist auch dadurch hinbiegen kannst, dass du deiner Klasse eine private Klasse gibst, die oeffenltich erbt und die virtuelle Methoden ueberschreibt, und dann eine Instanz per Komposition benutzt:

    class VirtualDings {
    public: 
      virtual void foo();
    };
    
    //Variante A: Erben zum ueberladen
    class VA : private VirtualDings{
    public:
      void bar() {foo();}
    private:
      virtual void foo(); //VirtualDings::foo() ueberladen
    };
    
    //Variante B: Privates Member das ueberlaedt.
    class VB {
    public:
      void bar() {ol.foo();}
    private:
      class Overload : public VirtualDings {
      public:
        virtual void foo();
      };
      Overload ol;  //komposition
    }
    

    Situation c): Du willst die Empty Base Class Optimization nutzen. Wenn die Klasse, mit der du deine Klasse implementieren moechtest, nur aus Methoden besteht und keine eigenen Daten beinhaltet, verbraucht sie als Member dennoch Platz. Als Basisklasse faellt das weg, wenn der Compiler EBCO implementiert hat.
    Bsp:

    struct Empty {
      void foo();
    };
    
    struct Inherits : private Empty {
      int i;
    };  //sizeof(Inherits) = sizeof(int)
    
    struct Komposes {
      int i;
      Empty e;
    }; //sizeof(Inherits) > sizeof(int)
    


  • Hi pumuckl,

    alles klar: weder a, noch b, noch c treffen bei mir zu. 🙂 => Komposition.


  • Mod

    Konrad Rudolph schrieb:

    GOTW #60 argumentiert, dass Komposition den Vorteil hat, eine Ausnahme-sichere Implementierung von op= zu vereinfachen.

    Im Prinzip ist der Artikel in dieser Hinsicht allerdings nicht überzeugend (der Entsprechende Artikel in Exceptional C++ ist besser).. Einerseits wird Ableitung von U vs. Zeiger auf U als Member gegenübergestellt - das ist nicht gerade Inheritance vs. Containment, denn dann müsste der Member vom Typ U sein. Die Sache mit der Exceptionsicherheit ist auch unklar. Die Variante mit Zeiger benutzt im Prinzip eine Form von Copy&Swap (das Swap ist ausgeschrieben und wird nicht in eine eigene Funktion verlagert, da ja der Zeiger auch ein roher ist) - Nichts hindert uns daran, genau das Gleiche bei Ableitung durchzuführen. Was Exceptionsicherheit angeht, ist ein Basisklassenmember im Prinzip ein normaler Member, der nur keinen eigenen Namen hat, wäre U ein normaler Member, würde die Argumentation nicht anders aussehen.


Log in to reply