wieso ist mehrfachvererbung schlecht?



  • macht sich das mi der mehrfachvererbung auch beim benötigten platz im stack/heap bemerkbar? denn wenn ich ein objekt einer vererbten klasse erstelle, wird doch auch immer ein objekt der basisklasse erstellt.

    mfg



  • terraner schrieb:

    macht sich das mi der mehrfachvererbung auch beim benötigten platz im stack/heap bemerkbar? denn wenn ich ein objekt einer vererbten klasse erstelle, wird doch auch immer ein objekt der basisklasse erstellt.

    Ja und?

    Irgendeine Beziehung brauchst du ja - folglich musst du auch die Klassen instanziieren. Einen Overhead gibt es nur bei virtual - aber der ist auch nicht besonders...



  • Zum Thema mehrere Methoden mit den selben Namen: Dynamisches Binden ist dann AFAIK nicht mehr länger möglich. Das wird also ziemlich madig, wenn man dann von der Klasse nochmal erbt.
    Deshalb ist es besser, wenn nur eine Basisklasse konkrete Methoden definiert.
    Man sollte nicht immer nur an seine Klasse denken, sondern auch an Klassen, die vielleicht später davon erben.



  • Optimizer schrieb:

    Zum Thema mehrere Methoden mit den selben Namen: Dynamisches Binden ist dann AFAIK nicht mehr länger möglich.[...]
    Deshalb ist es besser, wenn nur eine Basisklasse konkrete Methoden definiert.

    Das verstehe ich beides nicht. Warum bzw. was heisst es denn?



  • gibt es denn probleme beim thema polymorphie+mehrfachvererbung?



  • @Shade: Wenn du BasisKlasse::Methode aufrufen musst, da sonst zweideutig, ist das dynamische Binden futsch.

    Wenn nur eine Basisklasse konkrete Methoden definiert, musst du den Funktionsaufruf nicht so umständlich qualifizieren, so dass, weiterhin dynamisch gebunden kann.



  • Optimizer schrieb:

    @Shade: Wenn du BasisKlasse::Methode aufrufen musst, da sonst zweideutig, ist das dynamische Binden futsch.

    Wenn nur eine Basisklasse konkrete Methoden definiert, musst du den Funktionsaufruf nicht so umständlich qualifizieren, so dass, weiterhin dynamisch gebunden kann.

    Und genau das verstehe ich nicht.

    wenn ich

    void f(base const& b)
    {
      b.foobar();
    }
    
    f(Derived());
    

    mache, und Derived hat 2 Basisklasse wobei jede foobar() hat - gibt es ja trotzdem keine Probleme...



  • Ich entwickle momentan ein Framework und benutze auch häufig Mehrfachvererbung.
    Aber trotz aufwendiger Tests sind keine nennenswerten Probleme aufgetreten.

    Das Argument mit der Änderung der Basisklasse (bei public Vererbung) kann ich nicht nachvollziehen, normalerweise heißt public Vererbung "ist ein", d.h. Wenn sich die Superklasse verändert muss sich auch die Subklasse verändern, damit man weiterhin von "ist ein" reden kann.

    mfg



  • @Shade:

    class Foo { 
       virtual void bla () {} 
    }; 
    
    class Bar { 
       virtual void bla () {} 
    }; 
    
    class Derived  :  public Foo, public Bar
    {
    };
    

    bla() wird jetzt nicht mehr dynamisch gebunden, denn du musst ja immer angeben, welches bla() du aufrufen willst.

    Wenn du jetzt von dieser Klasse erbst und dort nochmal bla() redefinieren willst, ist die Verwirrung komplett:

    class Derived  :  public Foo, public Bar
    {
        virtual void bla();
    };
    

    Jetzt hast du gar keine Möglichkeit, die Derived-bla() Methode aufzurufen! (außer casten vielleicht)

    Und jetzt noch das Zuckerl: 😉
    Was ist wenn du sowohl Foo::bla() als auch Bar::bla() in einer abgeleiteten Klasse anders definieren willst, das geht gar nicht. Es ist unmöglich, da du nicht zwei mal void bla() deklarieren kannst.

    Den ganzen Ärger kann man vermeiden, wenn man es wie in Java/C# macht (jetzt geht gleich der Flamewar los 🙄 ) und nur eine Basisklasse konkrete Methoden (und bei Datenelementen gibt es ähnliche Probleme) hat.



  • ähm muss ich optimizers post verstehen? 😃



  • Wenn nur Funktionsnamen das Problem sind, seh ich keins, dann nennt man die Funktionen halt anders --> Fertig.

    Ok in großen Codefragmenten gibts da sicher ärger, aber ich kann mir nicht vorstellen, dass es dabei Probleme gibt.

    @otze: why?

    mfg



  • Glamdrink schrieb:

    Wenn nur Funktionsnamen das Problem sind, seh ich keins, dann nennt man die Funktionen halt anders --> Fertig.

    Dir sagt dynamisches Binden nicht wirklich was oder? :p



  • Optimizer schrieb:

    @Shade:

    class Foo { 
       virtual void bla () {} 
    }; 
    
    class Bar { 
       virtual void bla () {} 
    }; 
    
    class Derived  :  public Foo, public Bar
    {
    };
    

    bla() wird jetzt nicht mehr dynamisch gebunden, denn du musst ja immer angeben, welches bla() du aufrufen willst.

    Denk doch nicht nur technisch, sondern auch mal logisch. Wenn ich zwei Basisklassenhabe, wenn ich also zwei Sachen bin und beide Basisklassen bieten ein Verhalten, dann ist es doch klar, dass ich entweder mein Verhalten anpassen muss (überschreiben) oder aber, dass ich mich für ein Verhalten entscheiden muss. Alles andere ist schizophren.

    Optimizer schrieb:

    Wenn du jetzt von dieser Klasse erbst und dort nochmal bla() redefinieren willst, ist die Verwirrung komplett:

    class Derived  :  public Foo, public Bar
    {
        virtual void bla();
    };
    

    Jetzt hast du gar keine Möglichkeit, die Derived-bla() Methode aufzurufen! (außer casten vielleicht)

    Jetzt redest du wirr. Die bla-Methode in Derived überschreibt die bla-Methoden beider Basisklassen. Mal abgesehen davon, dass die Methoden alle private sind, würde der Aufruf von bla auf einem Derived-Objekt ganz korrekt Derived::bla aufrufen, egal ob über die Foo-, die Bar- oder die Derived-Schnittstelle.

    Was ist wenn du sowohl Foo::bla() als auch Bar::bla() in einer abgeleiteten Klasse anders definieren willst, das geht gar nicht.

    Ganz ruhig und erstmal Luft holen. Richtig ist, dass dies nicht einfach geht. Falsch ist es, dass es gar nicht geht. Ich habe hier in diesem Forum die Lösung für das "Siamesische Zwillingsproblem" schon mal gepostet.

    http://www.c-plusplus.net/forum/viewtopic.php?t=64542&highlight=com

    Dazu kommt, dass diese Probleme fast ausschließlich künstlicher Natur sind. Der Fall mit den Siamesischen Zwillingen tritt ja z.B. a) nur höchst selten und b) nur dann ein, wenn man keinen Zugriff auf den Source hat. In der Regel erbt man aber nicht unmotiviert von zwei völlig entfernten Klassen die man nicht verändern kann.

    Den ganzen Ärger kann man vermeiden, wenn man es wie in Java/C# macht (jetzt geht gleich der Flamewar los ) und nur eine Basisklasse konkrete Methoden (und bei Datenelementen gibt es ähnliche Probleme) hat.

    Der Zusammenhang ist mir jetzt unklar. Was genau machst du denn in Java, wenn du von einer Klasse erbst und ein Interface implementierst und Basisklasse und Interface haben eine Methode mit gleichem Namen?

    Die meisten Probleme der MI kommen imo daher, dass die Leute Vererbung falsch einsetzen. Sie verwenden Vererbung häufig da, wo eigentlich Komposition angebracht wäre. Die Probleme die aus diesem White-Box-Design resultieren sind bei SI schon problematisch, bei MI potenzieren sie sich aber.
    Fakt ist, dass es Situationen gibt, in denen MI angebracht ist (z.B. wenn jede einzelne Vererbung Sinn macht und keine Basisklasse eigentliche eine Basisklasse einer anderen Basisklasse ist, oder für Mix-Ins, oder für Policies oder für reine Interfaces...).
    Fakt ist auch, dass Workarounds für MI in der Regel zu hässlichen Code-Krücken führen.

    MI kann man außerdem entschärfen, wenn man grundsätzlich nur von Protokoll-Klassen (abstrakte Klassen ohne Daten und mit rein virtuellen Methoden)
    mehrfach erbt.

    Ich halte mich an die Regel:

    If you have an example of multiple inheritance in your design,
    assume you have made an error and prove otherwise.

    MI zu verteufeln, bzw. grundsätzlich abzulehnen, mit der Begründung sie wäre zu komplex oder unübersichtlich halte ich auf jeden Fall für völligen Käse.
    MI ist eine weitere Design-Option und wenn diese Option das Beste Kosten/Nutzen-Verhältnis hat, dann bin ich froh, wenn meine Sprache MI zulässt.



  • HumeSikkins schrieb:

    Der Zusammenhang ist mir jetzt unklar. Was genau machst du denn in Java, wenn du von einer Klasse erbst und ein Interface implementierst und Basisklasse und Interface haben eine Methode mit gleichem Namen?

    Gar nichts. Interface-Methoden sind immer abstract. Das ist übrigens das, was du mit "Protokollklassen" meinst. Diese enthalten nur abstrakte Methoden und keine Datenelemente. Das ist genau der Lösungsweg, den ich, genauso wie du, vorgeschlagen habe.

    Denk doch nicht nur technisch, sondern auch mal logisch. Wenn ich zwei Basisklassenhabe, wenn ich also zwei Sachen bin und beide Basisklassen bieten ein Verhalten, dann ist es doch klar, dass ich entweder mein Verhalten anpassen muss (überschreiben) oder aber, dass ich mich für ein Verhalten entscheiden muss. Alles andere ist schizophren.

    Da erscheint es mir aber eindeutig sinnvoller, ein Interface zu implementieren. Damit ist genauso sichergestellt, dass Derived sowohl vom Typ A als auch vom Typ B ist und trotzdem habe ich keine Mehrdeutigkeitskonflikte.
    Mir fällt jetzt vor allem auch kein Beispiel ein, wo es sinnvoll wäre, dass ein Objekt sowohl ein A als auch ein B ist, aber trotzdem nur die Methode einer Basisklasse aufruft.
    Und technisch hin oder her, das dynamische Binden ist im Eimer, was ich grundsätzlich schon mal nicht gut finde. Wohlgemerkt, dieses Problem hat man ja wirklich nur, wenn beide Basisklassen _konkrete_ Methoden definieren.

    Jetzt redest du wirr. Die bla-Methode in Derived überschreibt die bla-Methoden beider Basisklassen. Mal abgesehen davon, dass die Methoden alle private sind, würde der Aufruf von bla auf einem Derived-Objekt ganz korrekt Derived::bla aufrufen, egal ob über die Foo-, die Bar- oder die Derived-Schnittstelle.

    Stimmt, da hab ich mich jetzt vertan. Btw, das mit dem private ist jetzt nicht der Punkt. 😉



  • Optimizer schrieb:

    Interface-Methoden sind immer abstract.

    Ist mir bekannt.

    Das ist übrigens das, was du mit "Protokollklassen" meinst.

    Auch das ist mir bekannt.

    Das ist genau der Lösungsweg, den ich, genauso wie du, vorgeschlagen habe.

    C++-technisch sehe ich hier keinen Lösungsweg.
    In Java kann eine Basisklassenmethode als Implementation einer durch ein Interface deklarierte Methode in einer abgeleiteten Klasse dienen.
    Das ist in C++ aber nicht der Fall. Hier kann eine Basisklassenmethode niemals als "final overrider" einer rein virtuellen Methode einer anderen Basisklasse herhalten. Du musst die Methode also nach wie vor in der abgeleiteten Klasse überschreiben. Ansonsten bleibt die abgeleitete Klasse abstrakt.

    Da erscheint es mir aber eindeutig sinnvoller, ein Interface zu implementieren. Damit ist genauso sichergestellt, dass Derived sowohl vom Typ A als auch vom Typ B ist und trotzdem habe ich keine Mehrdeutigkeitskonflikte.

    Hä? Die Mehrdeutigkeitskonflikte entstehen durch den Namen und sind unabhängig davon, ob deine Basisklasse nun eine Protokoll-Klasse ist oder nicht.

    Und technisch hin oder her, das dynamische Binden ist im Eimer, was ich grundsätzlich schon mal nicht gut finde. Wohlgemerkt, dieses Problem hat man ja wirklich nur, wenn beide Basisklassen _konkrete_ Methoden definieren.

    Nö. Das Problem ist unabhängig davon ob eine Methode eine Implementation hat oder nicht. Wenn zwei Basisklassen eine Methode mit selben Namen haben, dann muss die abgeleitete Klasse diese Methode überschreiben oder sie kann über die Schnittstelle der abgeleiteten Klasse nicht aufgerufen werden.

    Tut mir leid. Ich kann dir wie so oft mal wieder nicht folgen.



  • C++-technisch sehe ich hier keinen Lösungsweg.
    In Java kann eine Basisklassenmethode als Implementation einer durch ein Interface deklarierte Methode in einer abgeleiteten Klasse dienen.
    Das ist in C++ aber nicht der Fall. Hier kann eine Basisklassenmethode niemals als "final overrider" einer rein virtuellen Methode einer anderen Basisklasse herhalten. Du musst die Methode also nach wie vor in der abgeleiteten Klasse überschreiben. Ansonsten bleibt die abgeleitete Klasse abstrakt.

    Stimmt, ich habe es eben ausprobiert. Ich war der festen Überzeugung, dass wenn nur eine ererbte Methode konkret ist, dass der Aufruf dann als eindeutig gilt. 😞
    Damit macht natürlich alles nachfolgende keinen Sinn mehr.



  • oh gott, fang niemals ne informatische diskussion an, wenn du nicht alle fachausdrücke auswendig kennst(und das in mehreren sprachen :D)
    was bedeuted komposition in bezug auf klassen?
    (und was sind interfaces)

    oh man, ich fühl mich so unwissend...



  • Komposition =

    Bjarne Stroustrup schrieb:

    Enthalten-Beziehung

    (siehe TC++PL S.794) 😉
    interfaces = (in Bezug auf Klassen) Schnittstelle, (im Bezug auf Software) Benutzerschnittstelle, sprich visueller Kram ...

    mfg
    Gladmring


Anmelden zum Antworten