wieso ist mehrfachvererbung schlecht?



  • also, ich hab mal hier als ich gestern abend nochmal nach infos über mehrfachvererbung gesucht hab,an mehreren stellen gelesen, dass mehrfachvererbung schlecht bzw teufelswerk ist.

    an andrer stelle wurde dazu noch ein verweis auf "thinking in c++ gebracht"
    das hab ich im internet gesucht und gefunden, und dort stand drin:

    Multiple inheritance

    You can inherit from one class, so it would seem to make sense to inherit from more than one class at a time. Indeed you can, but whether it makes sense as part of a design is a subject of continuing debate. One thing is generally agreed upon: You shouldn’t try this until you’ve been programming quite a while and understand the language thoroughly. By that time, you’ll probably realize that no matter how much you think you absolutely must use multiple inheritance, you can almost always get away with single inheritance.

    aber das ist ja eigentlich keine Begründung dafür, dass es nicht benutzt werden sollte, also: wo liegen die probleme der Mehrfachvererbung?

    Ich hab noch selber was nachgedacht, und vielleicht liegen die probleme einfach darin, dass es zu fehlern kommen kann, wenn in jeder basisklasse eine methode foo() definiert ist, aber mehr fällt mir auch nicht so ein..



  • Naja, ne andere Erklärung wäre, dass dein Programm unübersichtlich und schlecht wartbar wird. Wenn eine Klasse z. B. 5 Basisklassen hat, und du änderst auch nur eine davon, wirst du u. U. Probleme kriegen. Und dann musst du erstmal schauen, wo der Fehler lag. Wenn das nicht ordentlich designed ist, fällt dein ganzer Aufbau in sich zusammen.



  • Das 'Problem' mit Mehrfachvererbung ist IMHO nur, dass die Klassenhierachien sehr schnell sehr komplex werden koennen.

    Die anderen Argumente kann ich eigentlich nicht nachvollziehen.

    Mehrere Methoden mit dem selben Namen: da waere natuerlich ein rename wie in Eiffel recht praktisch - aber es geht ja auch ohne.
    Man kann ja Base1::foo und Base2::foo sagen 🙂

    Wenn eine Klasse 5 Basisklassen hat und man aendert eine, kann es natuerlich zu Problemen kommen wenn man nicht aufpasst. Selbiges gilt aber auch bei jeder anderen Beziehung zwischen Klassen.



  • Die Schwierigkeiten beginnen bei "KARO"-förmiger Vererbung:

    class VATER
    									{
    									public:
    										virtual int Geschlecht() = 0;
    									};
    
    								   /\
    								  /  \
    
    class SOHN : public VATER					class TOCHTER : public VATER
    {											{
    	int Geschlecht() { return 0; }				int Geschlecht() { return 1; }
    };											};
    
    								  \  /
    								   \/
    
    									class ENKEL : public SOHN, public TOCHTER
    									{
    									};
    
    VATER* mensch = new ENKEL;
    int Geschlecht = mensch->Geschlecht(); //???
    

    Andererseits spricht natürlich rein gar nichts dagegen, die Klasse WOHNMOBIL von den Klassen AUTO und HAUS abzuleiten: Ein Wohnmobil ist ein Auto und ist gleichzeitig ein Haus.



  • da waere natuerlich ein rename wie in Eiffel recht praktisch

    C++/CLI's Weg ist doch auch garnicht so schlecht:

    class Foo {
       virtual void bla () {}
    };
    
    class Bar {
       virtual void bla () {}
    };
    
    clas Baz : public Foo, public Bar {
       void bla () = Foo::bla {}
       void blub () = Bar::bla {}
    };
    

    Ich finde die Idee ganz gut.

    @Krösus:

    class SOHN : virtual public VATER ...
    
    class TOCHTER : virtual public VATER ...
    
    int Geschlecht = mensch->Geschlecht(); // kein Problem :)
    

    das geschlecht durch ein Int zurepräsentieren? Wäre da ein enum nicht schön.



  • Helium schrieb:

    C++/CLI's Weg ist doch auch garnicht so schlecht:

    Stimmt. Das schaut gut aus. Kannte ich aber noch nicht.
    Ist ja im Prinzip wie rename 🙂



  • Krösus schrieb:

    Andererseits spricht natürlich rein gar nichts dagegen, die Klasse WOHNMOBIL von den Klassen AUTO und HAUS abzuleiten: Ein Wohnmobil ist ein Auto und ist gleichzeitig ein Haus.

    und (abgesehen von dem inzestproblem) ist tochter kein vater.



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


Anmelden zum Antworten