Braucht man Mehrfachvererbung



  • Also ich benutze dann virtual, wenn ich es brauche. Nicht mehr und nicht weniger.



  • Artchi schrieb:

    Also ich benutze dann virtual, wenn ich es brauche. Nicht mehr und nicht weniger.

    Gute Programmierer programmieren gut 😉

    Es ist lediglich interessant sich durchschnittlichen oder eher schwächeren Code anzusehen. Da strotzt Java vor Vererbung und 100.000 Listener die überall hingeerbt werden und in C++ hat man dagegen nur ein paar functors die durch die Gegend fliegen.



  • Shade Of Mine schrieb:

    Es ist lediglich interessant sich durchschnittlichen oder eher schwächeren Code anzusehen. Da strotzt Java vor Vererbung und 100.000 Listener die überall hingeerbt werden und in C++ hat man dagegen nur ein paar functors die durch die Gegend fliegen.

    Ja, nur in Java hast du nicht sowas wie Functors. Nennt sich in anderen Sprachen glaube ich Delegate? In Java bleibt dir somit nichts anderes übrig als das Listener-Interface zu "vererben".

    Und natürlich finde ich std::tr1::function toll, dann brauche ich keine Vererbung, um eine beliebige (Member-) Function zu verbinden (std::tr1::bind). Ein virtual kann man so natürlich einsparen.

    struct Observer
    {
        void update();
    };
    
    Observer obs;
    std::tr1::function<void (void)> obs_fun = std::tr1::bind(&Observer::update, &obs);
    
    // später:
    obs_fun();  // ruft  obs.update() auf
    

    obs_fun kann natürlich jede andere beliebige (Member-)Function binden.

    EDIT: function und bind kommen auch in C++0x rein, und somit direkt im Namespace std.



  • Shade Of Mine schrieb:

    Es ist lediglich interessant sich durchschnittlichen oder eher schwächeren Code anzusehen. Da strotzt Java vor Vererbung und 100.000 Listener die überall hingeerbt werden und in C++ hat man dagegen nur ein paar functors die durch die Gegend fliegen.

    Sehr objektiver Sprachenvergleich. 👍 🤡



  • Artchi schrieb:

    Ja, nur in Java hast du nicht sowas wie Functors. Nennt sich in anderen Sprachen glaube ich Delegate? In Java bleibt dir somit nichts anderes übrig als das Listener-Interface zu "vererben".

    Schlechter Java Code:

    public class Frame1 extends JFrame implements AListener, BListener, CListener, DListener... {
    ...
      foo.registerFooListener(this);
      bar.registerBarListener(this);
    }
    

    Guter Java Code:

    public class Frame1 extends JFrame {
    ...
      foo.registerFooListener(new FooListener() {
        ...
      });
      bar.registerBarListener(new BarListener() {
        ...
      })
    
    }
    

    aber scheinbar sind wir noch nicht reif genug um über praktiken zu sprechen die sich eingebürgert haben...



  • Du hast Anonyme Klassen... schön, habe ich hier in meinen Projekten auch. Trotzdem ist es Vererbung! Nur das am Ende die spezielle Implementierung keinen Namen hat. Du hast das Interface-Vererben-Problem nicht umgangen, nur verlagert. Von den Interface bin ich immer noch abhängig und Polymorphy ist es trotzdem noch!



  • Artchi schrieb:

    Du hast Anonyme Klassen... schön, habe ich hier in meinen Projekten auch. Trotzdem ist es Vererbung! Nur das am Ende die spezielle Implementierung keinen Namen hat. Du hast das Interface-Vererben-Problem nicht umgangen, nur verlagert. Von den Interface bin ich immer noch abhängig und Polymorphy ist es trotzdem noch!

    Habe ich je gesagt dass das das Problem ist?

    Das Problem sind tiefe und breite Vererbungshierachien.



  • Das grundsätzliche Problem bei der Implementation der Mehrfachvererbung ist doch die Relativität der Objektadresse. Als unmittelbare Konsequenz sind C++-Methodenzeiger für Delegate-Zwecke nahezu unbrauchbar, wie Don Clugston anschaulich darlegt. Auch laufen dadurch sich ergebende Unausweichlichkeiten im Umgang mit Objektzeigern dem intuitiven Verständnis des Programmcodes zuwider. Wie sollte man auf die Idee kommen, daß eine harmlose Anweisung wie

    Base* b;
    Derived* d;
    ...
    b = d;
    

    die Zeigeradresse verändert? Und gesetzt, Derive leite von mehreren Klassen ab, die ihrerseits von Base abstammen, was bei polymorphielastiger Programmierung vorkommen kann: mit welch überflüssiger Verzweiflung reagiert ein Novize auf die dröge Verweigerung des Compilers, dasselbe Stück Codes zu übersetzen?

    Mit Interfaces im herkömmlichen Sinne - so wie sie auch in Delphi und vermutlich in Java existieren - wird dieses Problem vermieden, da sie keine Datenelemente enthalten. Das hat sich in vielen Situationen, ob nun Java, .NET oder COM, als sinnvoll erwiesen (wenngleich in Java ein gewisses Überreizungspotential bestehen mag). Nach einer praktischen und alternativlosen Anwendung der Mehrfachvererbung von C++ jedoch suche ich noch immer vergeblich.



  • Technisch zwar sehr schoen, aber was interessiert mich das als Programmierer?

    Wenn man eine schoene Library wie zB boost oder dergleichen hat, wird die ganze Implementation vor einem versteckt. Deshalb _kann_ ich Member function pointer problemlos benutzen, oder aber functors oder aber den Java Ansatz mit laufzeit Polymorphie.

    Ich habe in C++ also alle 3 Ansaetze die problemlos funktionieren:

    Delegates (methodenzeiger)
    Interfaces (laufzeitpolymorphie)
    functors (compiletime polymorpgie)

    Wo genau ist das jetzt schlecht? Dass bei Methodenzeigern intern furchtbare Sachen geschehen stimmt ja - aber andererseits: was bei einer referenzierung eines Objektes in Java intern passiert, ist auch nicht schoen. Dauernd wird das Objekt umherkopiert - der Zeiger aendert sich also auch 😉

    Interessiert aber Niemanden, da es perfekt versteckt ist.



  • Ohne Mehrfachvererbung sind einige Probleme nicht OO-mäßig lösbar.

    Objektorientierung wird mit nichten durch Vererbung geschweige denn Mehrfachvererbung definiert. Sie setzt sie nicht einmal zwingend voraus.
    Häufig sind Alternativen wie Komposition/Delegation oder sonstige Entwurfsmuster besser (um nicht zu sagen "objektorientierter") als Vererbung.



  • Shade Of Mine schrieb:

    Technisch zwar sehr schoen, aber was interessiert mich das als Programmierer?

    Wie kann ein C++-Programmierer sich ernsthaft eine solche Frage stellen? Noch nie im Assembler-Debugger gelandet?

    Shade Of Mine schrieb:

    Wenn man eine schoene Library wie zB boost oder dergleichen hat, wird die ganze Implementation vor einem versteckt. Deshalb _kann_ ich Member function pointer problemlos benutzen, oder aber functors oder aber den Java Ansatz mit laufzeit Polymorphie.

    Ich habe in C++ also alle 3 Ansaetze die problemlos funktionieren:

    Delegates (methodenzeiger)
    Interfaces (laufzeitpolymorphie)
    functors (compiletime polymorpgie)

    Bekanntermaßen ermöglicht C++ insbesondere mittels TMP die Möglichkeit, so ziemlich jedes Paradigma umzusetzen, sei es noch so schmerzhaft. Die Sprache bietet andererseits, und damit bleibt sie in der Nähe des Ideals von C, sehr wenig Compilermagie. Infolgedessen müssen alle Sprachdefizite durch Bibliotheken ausgeglichen werden. Das erhöht die Kompilierzeiten, die Fehleranfälligkeit, die Uneinheitlichkeit und die Komplexität.

    Um aber dem unbedingten Ideal der Plattformunabhängigkeit zu genügen, offeriert die Standardbibliothek zumeist eher minimalistische Hilfsmittel. Selbst so grundlegende Dinge wie funktionierende Funktionszeiger muß man über Boost nachrüsten (oder selbst schreiben, falls die Boost-Lösung nicht ausreichend performant oder in anderer Hinsicht unzulänglich ist; Stichwort Uneinheitlichkeit). Es wäre so unsagbar einfach gewesen, analog zu Borlands C++Builder-Compilererweiterung __closure ein Schlüsselwort zu spezifizieren, das eine einheitliche Syntax für an ein Objekt gebundene Methodenzeiger einführt. Sie sind wesentlich leichter zu implementieren als Methodenzeiger, außerdem maximal performant. Aber dafür wäre ja ein neues Schlüsselwort notwendig gewesen. Da nimmt man dann lieber den Boost-Workaround in den nächsten Standard auf.

    Das ist natürlich kein Einzelfall. Glücklicherweise gibt es durchaus positive Ansätze im neuen Standardentwurf, z.B. die Neuinterpretation des auto-Schlüsselwortes. Von den produktiven Möglichkeiten einer wirklich innovativen Sprache wie z.B. Chrome ist C++ aber dennoch weit entfernt. Und die Komplexität, die C++0x erreichen wird, ist für einen Neueinsteiger unüberschaubar bis abschreckend.

    Nun bin ich aber ein wenig vom Thema abgekommen 😉
    Kurzum, hast du Mehrfachvererbung schon einmal benötigt? Nein? Dann stimmst du mir sicherlich darin zu, daß es ohne erkennbaren Mehrwert zusätzliche Komplexität verursacht und ein eigentlich ganz praktisches Sprachfeature wie Methodenzeiger verkrüppelt.

    Shade of Mine schrieb:

    Wo genau ist das jetzt schlecht? Dass bei Methodenzeigern intern furchtbare Sachen geschehen stimmt ja - aber andererseits: was bei einer referenzierung eines Objektes in Java intern passiert, ist auch nicht schoen. Dauernd wird das Objekt umherkopiert - der Zeiger aendert sich also auch 😉

    In einer Bytecode-Sprache ist das dann doch noch etwas anderes als in einem besseren C-Derivat 😉



  • audacia schrieb:

    Wie sollte man auf die Idee kommen, daß eine harmlose Anweisung wie

    Base* b;
    Derived* d;
    ...
    b = d;
    

    die Zeigeradresse verändert?

    Gegenfrage: warum sollte man sich überhaupt irgendwelche Gedanken über Adressen machen müssen?



  • audacia schrieb:

    Shade Of Mine schrieb:

    Technisch zwar sehr schoen, aber was interessiert mich das als Programmierer?

    Wie kann ein C++-Programmierer sich ernsthaft eine solche Frage stellen? Noch nie im Assembler-Debugger gelandet?

    In produktivsystemen? Nein. Zum rumspielen: oefters.

    Es wäre so unsagbar einfach gewesen, analog zu Borlands C++Builder-Compilererweiterung __closure ein Schlüsselwort zu spezifizieren, das eine einheitliche Syntax für an ein Objekt gebundene Methodenzeiger einführt.

    Komplett realitaetsfern.

    Sorry aber _alles_ ist so leicht implementierbar. Threads? klar, machen wir ein __thread keyword. sockets? gogo __socket. Wie waers mit multicore suppoert: __multicore {}.

    Nur leider geht es komplett an der realitaet vorbei. Zu dem zeitpunkt als der C++ standard entschieden wurde (mitte der '90er) waren closures und delegates kein essentielles notwendiges feature - du kannst es mit einer library ja problemlos implementieren.

    der neue c++ standard hat closures. wie das implementiert wird, ist komplett egal.

    Von den produktiven Möglichkeiten einer wirklich innovativen Sprache wie z.B. Chrome ist C++ aber dennoch weit entfernt.

    Und jetzt denk mal nach warum C++, Java, C# und Konsorten soviel erfolg haben. Liegt es daran dass die Sprachen so toll designed sind? Nein - es liegt daran dass diese Sprachen das know how verwenden das die leute haben.

    Kurzum, hast du Mehrfachvererbung schon einmal benötigt? Nein? Dann stimmst du mir sicherlich darin zu, daß es ohne erkennbaren Mehrwert zusätzliche Komplexität verursacht und ein eigentlich ganz praktisches Sprachfeature wie Methodenzeiger verkrüppelt.

    Dauernd. Ich sehe sie auch dauernd. Ich habe noch kein Java Programm gesehen dass nicht mehrfachvererbung einsetzt.

    Lies mal meinen Beitrag mit ueber konkreten Klassen und Konzepte.

    Mehrfachvererbung wird sehr oft eingesetzt und ist meistens etwas gutes. Die Leute erschrecken nur davor, weil sie immer an die schlechten beispiele denken.

    Aber was das wirkliche Problem ist, ist das erben von konkreten Klassen.

    Shade of Mine schrieb:

    Wo genau ist das jetzt schlecht? Dass bei Methodenzeigern intern furchtbare Sachen geschehen stimmt ja - aber andererseits: was bei einer referenzierung eines Objektes in Java intern passiert, ist auch nicht schoen. Dauernd wird das Objekt umherkopiert - der Zeiger aendert sich also auch 😉

    In einer Bytecode-Sprache ist das dann doch noch etwas anderes als in einem besseren C-Derivat 😉

    Warum?

    PS:
    ich sehe in Chrome nichts besonderes wo ich sagen wuerde "wow". Ein paar features sehen nett aus, aber das alleine macht die sachen nicht wirklich sonderlich interessant. Vorallem da es eine fuelle an schluesselwoertern gibt - sowas ist idR ein schlechtes zeichen.



  • tfa schrieb:

    Objektorientierung wird mit nichten durch Vererbung geschweige denn Mehrfachvererbung definiert. Sie setzt sie nicht einmal zwingend voraus.
    Häufig sind Alternativen wie Komposition/Delegation oder sonstige Entwurfsmuster besser (um nicht zu sagen "objektorientierter") als Vererbung.

    Sofern wir uns hier im Kontext statisch getypter OO-Sprachen wie C++ oder Java bewegen, würde ich gerne wissen, was dir die Komposition oder Delegation bringt, wenn die Objekte, an die du delegierst nicht polymorph sein dürfen.



  • Bashar schrieb:

    Sofern wir uns hier im Kontext statisch getypter OO-Sprachen wie C++ oder Java bewegen, würde ich gerne wissen, was dir die Komposition oder Delegation bringt, wenn die Objekte, an die du delegierst nicht polymorph sein dürfen.

    In Java kannst Du dank Interfaces problemlos Vererbung durch Komposition + Delegation ersetzen und trotzdem noch Polymorphie nutzen. Du definierst einfach ein Interface + eine Implementierung dieses Interfaces. Diese Implementierung kannst Du dann per Komposition in Deinen Objekten benutzen (statt davon zu erben). Zusätzlich implementieren diese Objekte dann noch das Interface und delegieren auf das Kompositum.



  • byto schrieb:

    In Java kannst Du dank Interfaces problemlos Vererbung durch Komposition + Delegation ersetzen und trotzdem noch Polymorphie nutzen.

    Ein Interface zu implementieren ist für meine Begriffe auch Vererbung (der Schnittstelle). Falls man zwischen Vererbung und Subtyping einen Unterschied macht, stimmt das natürlich.



  • Also ich hab mir den Thread nicht durchgelesen, aber geb mal meinen Senf dazu ab.
    Bei komplett eigenen Klassen kann mans vermeiden, denke ich, aber ansonsten ises ganz praktisch, wenn es zum Beispiel ein Fenster ist und von einer GUI Klasse erbt und du noch eine andere Vererbung rein bringen willst, oder so etwas ähnliches.
    Kann aber auch sonst praktisch sein, für SFML brauchst du ein SFML Fenster zum rein zeichnen, wie integriert man das in wxWidgets? Eine Klasse die von wxWindow und sf::Window erbt, dann brauchte man eigentlich nur noch ein paar Zeilen Code, nich viel.



  • @Bashar: Jo, Java-Interface-Implementierung ist im Grunde ja nichts anderes als Vererbung. Aber man liest halt immer wieder als Kritik am Interface-Konzept von Java, dass man damit Code duplizieren muss. Und das ist halt schlicht falsch, wenn mans auf oben beschriebenem Weg macht.



  • byto schrieb:

    @Bashar: Jo, Java-Interface-Implementierung ist im Grunde ja nichts anderes als Vererbung. Aber man liest halt immer wieder als Kritik am Interface-Konzept von Java, dass man damit Code duplizieren muss. Und das ist halt schlicht falsch, wenn mans auf oben beschriebenem Weg macht.

    Wie genau komm ich an IFooable und AbstractFoo vorbei?



  • camper schrieb:

    Gegenfrage: warum sollte man sich überhaupt irgendwelche Gedanken über Adressen machen müssen?

    Schreibt der Standard vor, daß static_cast <Base*> (reinterpret_cast <Derived*> (0)) == 0 ist, auch wenn Base nicht am Beginn des Objektlayouts von Derived liegt? Falls nicht, müßte man p ? static_cast <Base*> (p) : 0 schreiben.

    Ein anderer, wesentlich unangenehmerer Aspekt im Zusammenhang mit Methodenzeigern: seltsamerweise darf (oder wenigstens: sollte) man einem Methodenzeiger für eine Basisklasse keine Methode einer abgeleiteten Klasse zuweisen. Tut man es doch (oder erzwingt es mittels Cast), so passieren haarsträubende Dinge:

    class Base
    {
    protected:
        int i;
    
      public:
        Base (void) : i (42) {}
        int func(void) { return i; };
    };
    typedef int (Base::* basemember_t) (void);
    
    class Base2
    {
        int j;
    
      public:
        Base2 (void) : j (1337) {}
    };
    
    class Derived : public Base2, public Base
    {
      public:
        int new_func(void) { return i; };
    };
    
    void foo (void)
    {
      Derived d;
      int i;
      basemember_t m = static_cast <basemember_t> (&Derived::new_func);
      i = (d.*m) ();
    }
    

    Dies scheint legales C++ zu sein, jedenfalls kompilieren Comeau, MSVC und BCC das anstandslos (MSVC ist wenigstens so entgegenkommend und warnt vor möglichen Konsequenzen). Ob aber der Wert von i nun erwartungsgemäß 42 oder aber undefiniert ist, hängt schlicht davon ab, in welcher Reihefolge die Basisklassen von Derived genannt werden.

    Falls du meinst, so etwas tue man doch nicht: Erstens wird fast alles, was der Compiler erlaubt, auch getan, und zweitens tut Microsoft das höchstselbst in den MFC.

    Shade Of Mine schrieb:

    Komplett realitaetsfern.

    Sorry aber _alles_ ist so leicht implementierbar. Threads? klar, machen wir ein __thread keyword. sockets? gogo __socket. Wie waers mit multicore suppoert: __multicore {}.

    Nur leider geht es komplett an der realitaet vorbei. Zu dem zeitpunkt als der C++ standard entschieden wurde (mitte der '90er) waren closures und delegates kein essentielles notwendiges feature - du kannst es mit einer library ja problemlos implementieren.

    der neue c++ standard hat closures. wie das implementiert wird, ist komplett egal.

    Du machst es dir aber sehr einfach damit, meine Argumentation ad absurdum zu führen 😉

    Um vorab einem Mißverständnis vorzubeugen: obgleich es so heißt, hat __closure nichts mit dem zu tun, was heutzutage als Closure bezeichnet wird; eher mit Delegates. Closures sind recht schwierig zu implementieren, außerdem auch eine eher neue Erscheinung.

    Zum Zeitpunkt der Verabschiedung des C++-Standards waren Delegates immerhin so gefragt, daß Borland sich schon für die OWL mit einer Compilererweiterung behelfen mußte und Qt das präprozessorgestützte Signal/Slot-System einführte, um genau diesem Mangel beizukommen. Und vor 1998 waren die meisten Compiler ohnehin nicht sattelfest genug, um all die templatebasierten Workarounds, die in der Theorie möglich und heute in Boost implementiert sind, verstehen zu können.

    Solltest du weiterhin daran zweifeln, so empfehle ich dir dringend, oben bereits verlinkten Artikel von Don Clugston zu lesen.

    Shade Of Mine schrieb:

    der neue c++ standard hat closures. wie das implementiert wird, ist komplett egal.

    Nein.

    Shade Of Mine schrieb:

    Dauernd. Ich sehe sie auch dauernd. Ich habe noch kein Java Programm gesehen dass nicht mehrfachvererbung einsetzt.

    Ich fragte dich nach Mehrfachvererbung, nicht nach der Implementation mehrerer Interfaces. Hättest du meinen vorletzten Post genau gelesen, hättest du feststellen können, daß ich ausdrücklich dazwischen unterscheide:

    *this schrieb:

    Mit Interfaces im herkömmlichen Sinne - so wie sie auch in Delphi und vermutlich in Java existieren - wird dieses Problem vermieden, da sie keine Datenelemente enthalten. Das hat sich in vielen Situationen, ob nun Java, .NET oder COM, als sinnvoll erwiesen (wenngleich in Java ein gewisses Überreizungspotential bestehen mag). Nach einer praktischen und alternativlosen Anwendung der Mehrfachvererbung von C++ jedoch suche ich noch immer vergeblich.

    Shade Of Mine schrieb:

    Aber was das wirkliche Problem ist, ist das erben von konkreten Klassen.

    Welches Mehrfachvererbung heißt 😉

    Daß eine Vielzahl an Schlüsselwörtern eine schlechte Sprache ausmacht, ist ein Vorurteil von C- und C++-Programmierern.


Anmelden zum Antworten