"to MI or not to MI"



  • Hallo Leute,

    Da mich das Thema interessiert, moechte ich hier gerne die MI-Diskussion aus diesem Thread fortsetzen: http://www.c-plusplus.net/forum/314127

    Einerseitz finde ich Xins Ansatz interessant, rein logisch kann ich dem jedoch (noch) nichts abgewinnen. Darum moechte ich Xin bitten, ein nicht konstruiertes Modell aus realem Code zu zeigen und zu erklaeren. Beispiele tendieren dazu, als Diskussionsgrundlage ungeeignet zu sein. Vielleicht lerne ich ja etwas dazu - bisher habe ich MI kaum verwendet.

    Ich finde auch, dass es sehr wichtig ist, zwischen 2 Arten von Vererbung zu differenzieren (ich erfinde hierfuer jetzt 2 neue Begriffe):
    - statische Vererbung: Eine Basisklasse ist als Implementierungshilfe gedacht, aber sie dient nicht als Ueberbegriff. (keine ist-ein Beziehung) Eine solche Klasse hat ueblicherweise keine virtuellen Memberfunktionen und einen protected, nonvirtual dtor.
    - dynamische Vererbung: Das, was man beigebracht bekommt, wenn man Vererbung lernt. Eine Ente erbt von Saeugetier, ein Auto von Fahrzeug. Die Basisklasse hat virtuelle Memberfunktionen und einen public, virtual dtor.

    Uebrigens denke ich, dass das urspruengliche Problem der Diskussion einfach ist, dass Xins Filename wohl eher ThingyOnDisk heissen sollte - oder so aehnlich. Habe ich das richtig verstanden?
    Ich bitte darum, die Diskussion konstruktiv fortzusetzen und nicht wieder gegenseitig aufeinander rumzuhacken - das fuehrt zu nichts.

    Gruesse,
    Der Kellerautomat



  • Bevor dieses Kleinod der Biologie einem edit zum Opfer fällt:

    Kellerautomat schrieb:

    - dynamische Vererbung: Das, was man beigebracht bekommt, wenn man Vererbung lernt. Eine Ente erbt von Saeugetier, ein Auto von Fahrzeug

    SCNR 🙂



  • lol, das kommt davon, wenn man zu viel ans Programmieren denkt 😃



  • - statische Vererbung: Eine Basisklasse ist als Implementierungshilfe gedacht, aber sie dient nicht als Ueberbegriff. (keine ist-ein Beziehung) Eine solche Klasse hat ueblicherweise keine virtuellen Memberfunktionen und einen protected, nonvirtual dtor.

    Solche Klassen sind als final zu betrachten und nicht fuer die Vererbung zu benutzen. Faellt also Aus. Warum? google!

    Dann sollte man erstmal schauen, wo Vererbung eingesetzt wird. Sicher koennen gute Window-Toolkits damit designed werden. Noch was?

    Ja, mir fehlt immernoch ein Beispiel fuer Mehrfachvererbung, bei dem man es ohne nicht offensichtlich besser machen kann.



  • knivil schrieb:

    ...

    Solche Klassen sind als final zu betrachten und nicht fuer die Vererbung zu benutzen. Faellt also Aus. Warum? google! ...

    Bitte Suchwörter angeben, dann google ich. Kann den Worten so nicht folgen. "Helper Base Classes" o.ä. führte nicht zum Erfolg. So wie Du es beschreibst, klingt es so, als dürfe man die Helferbasisklasse nicht als Basisklasse verwenden. Wär ja blöde irgendwie, wie sonst kann man default-Implementierungen für Schnittstellen bieten, ohne ein komplettes Schnittstellen-Forwarding zu einem Member zu betreiben, oder eine weitere QuerySchnittstelle-Schnittstelle zu definieren?



  • knivil schrieb:

    Solche Klassen sind als final zu betrachten und nicht fuer die Vererbung zu benutzen. Faellt also Aus. Warum? google!

    Ganz und gar nicht. Ich denke, du wuerdest stattdessen Komposition benutzen, richtig? Das hat jedoch den Nachteil, dass man alle Memberfunktionen an den Member delegieren muss. CRTP sei hier als praktische Technik genannt, die Gebrauch von dieser Art Vererbung macht.

    knivil schrieb:

    Dann sollte man erstmal schauen, wo Vererbung eingesetzt wird. Sicher koennen gute Window-Toolkits damit designed werden. Noch was?

    Bitte setze Verebung nicht mit polymorphie gleich. Das ist nicht dasselbe. (siehe "statische Vererbung")
    Dynamische Vererbung (aka Polymorphie) setze ich ebenfalls sehr sparsam ein.



  • nonvirtual dtor

    http://www.parashift.com/c++-faq/virtual-dtors.html

    "Helper Base Classes" o.ä. führte nicht zum Erfolg

    Erklaer mal warum Vererbung anstatt Aggregation benutzt werden sollte.

    Bitte setze Verebung nicht mit polymorphie gleich. Das ist nicht dasselbe. (siehe "statische Vererbung")

    Bei mir gibt es in C++ nur statische Polymorphie via Templates/Overloading oder dynamische Polymorphie via Vererbung. Deine "statische Vererbung" ist mir erstmal nicht klar.

    CRTP sei hier als praktische Technik genannt, die Gebrauch von dieser Art Vererbung macht

    Das ist was sehr spezielles und steht glaube nicht zur Diskussion.



  • Damit die designierte Benutzerklasse von außen betrachtet eine Schnittstelle implementiert, ohne dass sie, von innen betrachtet, sich um alle in den meisten, aber nicht allen Fällen (ansonsten wäre ja die Schnittstelle entsprechend zu vereinfachen), wiederkehrenden Details kümmern muss.
    Schön wäre, wenn man diese Zwischenklasse irgendwie privat machen könnte, ohne damit gleicht auch die Schnittstelle privat zu machen.
    Edit: Oh, Kellerautomat bezog sich ja auf nonvirtual-dtor. Was ich meine hat natürlich über die Schnittstelle schon den Typ des Dtors vorgegeben.



  • knivil schrieb:

    Deine "statische Vererbung" ist mir erstmal nicht klar.

    struct Base
    {
    	void f();
    
    protected:
    	~Base();
    };
    
    struct Derived : Base
    {};
    

    --> Derived implementiert ein "statisches" Interface, das f() braucht, aber die Implementierung der Funktion liegt in Base.



  • Gut, verstanden. Und bei welchen Gelegenheiten wird es eingesetzt?

    Mir fehlt immer noch ein gutes einsichtiges Beispiel aus der Praxis. Da ich es wahrscheinlich in den meisten Faellen anders mache, fehlt da die Erfahrung.



  • Expression Templates (CRTP), oder wenn man einfach nur ein Concept (so heisst statisches Interface hoffentlich in C++17) hat, fuer das man default-Implementierungen hat. Oder Policy-Based Class Design. Konkret habe ich es verwendet bei der Implementierung von Vektoren und Matrizen, sowie ein paar kleinen Utilities.

    Uebrigens ist boost::noncopyable ein sehr gutes Beispiel dafuer.



  • Gut werde mal policy design nochmal in Modern C++ nachlesen. Vererbung haette ich trotzdem nicht benutzt.

    Mein konkretes Beispiel: Histogram_match haengt von einem Aehnlichkeitsmass ab, dass benutzt wird (als "Policy", vielleicht ist es auch keine). Da gibt es dann mehrere Umsetzungen:

    template<typename S>
    struct histogram_match
    {
        // map an rgb image to an image where every pixel contains similarity values
        apply(image<rgb> in, image<float> out) const
        {
            S similarity_measure;
        }
    }
    

    oder wenn Zustand gespeichert werden soll:

    template<typename S>
    struct histogram_match
    {
        // map an rgb image to an image where every pixel contains similarity values
        apply(image<rgb> in, image<float> out) const
        {
        }
        private:
            S similarity_measure;
    }
    

    oder

    struct histogram_match
    {
        // map an rgb image to an image where every pixel contains similarity values
        template<typename S>
        apply(image<rgb> in, image<float> out) const
        {
            S similarity_measure;
        }
    }
    

    Aber vielleicht ist das garnicht policy based design.

    Niemals wuerde ich machen:

    struct histogram_match : S
    {
    }
    

    Uebrigens ist boost::noncopyable ein sehr gutes Beispiel dafuer.

    Das genau wieviel Implementierung besitzt? Aber, das akzeptiere ich als einfaches nachvollziehbares Beispiel. Und auch als Beispiel fuer Mehrfachvererbung wenn man andere Eigenschaften wie moveable hinzukommen.



  • knivil schrieb:

    Niemals wuerde ich machen:

    struct histogram_match : S
    {
    }
    

    Damit hast du aber den Vorteil, dass die Policy das Interface erweitern kann.

    knivil schrieb:

    Das genau wieviel Implementierung besitzt? Aber, das akzeptiere ich als einfaches nachvollziehbares Beispiel. Und auch als Beispiel fuer Mehrfachvererbung wenn man andere Eigenschaften wie moveable hinzukommen.

    Hier ein Beispiel aus der realen Welt (das ich in erweiterter Form auch so umgesetzt habe): Du schreibst eine Mathematik-Bibliothek fuer 3D Anwendungen. Da gibt es "reine" Vektoren, Farben, Punkte, etc. Alle von diesen sind aber letztlich irgendwie Vektor-artig. Ausserdem haben sie gemeinsam, dass du gerne Zugriff auf ihre Daten haettest, damit du diese an ein Grafik-API wie OpenGL oder DX weitergeben kannst. Da das ganze effizient sein soll, scheidet Polymorphie schon von vornherein aus, auch will man Value-Semantiken.

    Oft wird dann jede dieser Klassen fuer sich geschrieben, was 2 Nachteile hat:
    - Man sollte das Interface zum Datenzugriff konsistent halten
    - Code Duplikation

    Hier koennte man beispielsweise eine statische Basisklasse vector_storage einfuehren. Diese hat intern einen Array-Member fuer die Daten und stellt den [] Operator sowie .data() bereit. Auch koennte diese Klasse haeufig gebrauchte Operationen implementieren, z.B. Vektoraddition.
    Man leitet einfach vector, color und point von vector_storage ab und loest beide Probleme.



  • Kellerautomat schrieb:

    Da mich das Thema interessiert, moechte ich hier gerne die MI-Diskussion aus diesem Thread fortsetzen: http://www.c-plusplus.net/forum/314127

    Ne, da ist nichts interessantes. Ehrlich nicht.

    Vererbung ist seit etlichen Jahren ziemlich gut verstanden - da gibt es nichts neues mehr.

    Was interessant ist sind Mixins. Das ist ein Thema das noch nicht 100% verstanden ist. Es gibt unterschiedliche Implementierungen und Ansaetze dazu. Wenn dich das Thema also interessiert, schau dir Mixins an.

    In C++ macht man die so: http://www.drdobbs.com/cpp/mixin-based-programming-in-c/184404445

    Dein letztes Beispiel macht uebrigens keinen Sinn. Wenn du hier ableiten willst, dann tue es einfach. Das ist kein neues Konzept oder so: man muss nicht jede Vererbung immer Polymorph nutzen. Logischer waere es aber, nicht zu Vererben sondern einfach vector_storage als member zu haben.

    Denn alles was dir eine Ableitung bringt, ist die schreibarbeit fuer data() und op[] zu ersparen.

    Stell dir mal vor, du willst statt vector_storage eine andere Klasse nehmen. Dann kannst du das in der Vererbungsvariante nicht mehr machen - weil du sonst dein oeffentliches Interface ja anpassen musst. Wenn du aber korrekterweise hier Aggregation verwendet hast, ist es kein Problem - da es nur ein Implementierungsdetail deiner Klasse ist.

    Vererbung will man eigentlich nie haben. Sie ist furchtbar. Manchmal ist sie notwendig, dann muss man sie halt verwenden. Aber es hat seinen Grund warum es Objekt Orientierte Sprachen gibt, die keine Vererbung unterstuetzen. Vererbung ist eigentlich nur fuer eins gut: ueberschreiben von virtuellen Funktionen.

    PS:
    Das Interface einer Klasse lässt sich auch super einfach per freistehenden Funktionen erweitern. Man muss nicht immer die Klasse anfassen nur weil man Funktionalität hinzufügen will.

    Und Templates ermöglichen hier generische Implementierungen.



  • Shade Of Mine schrieb:

    Denn alles was dir eine Ableitung bringt, ist die schreibarbeit fuer data() und op[] zu ersparen.

    Das ist doch das Ziel.

    Shade Of Mine schrieb:

    Stell dir mal vor, du willst statt vector_storage eine andere Klasse nehmen. Dann kannst du das in der Vererbungsvariante nicht mehr machen - weil du sonst dein oeffentliches Interface ja anpassen musst. Wenn du aber korrekterweise hier Aggregation verwendet hast, ist es kein Problem - da es nur ein Implementierungsdetail deiner Klasse ist.

    Dann haelt sich der schreiber dieser Klasse besser an das Concept "Storage".



  • Kellerautomat schrieb:

    Shade Of Mine schrieb:

    Denn alles was dir eine Ableitung bringt, ist die schreibarbeit fuer data() und op[] zu ersparen.

    Das ist doch das Ziel.

    Nein. Schreibarbeit sparen soll dir deine IDE, nicht Vererbung.

    Shade Of Mine schrieb:

    Stell dir mal vor, du willst statt vector_storage eine andere Klasse nehmen. Dann kannst du das in der Vererbungsvariante nicht mehr machen - weil du sonst dein oeffentliches Interface ja anpassen musst. Wenn du aber korrekterweise hier Aggregation verwendet hast, ist es kein Problem - da es nur ein Implementierungsdetail deiner Klasse ist.

    Dann haelt sich der schreiber dieser Klasse besser an das Concept "Storage".

    Den Satz verstehe ich nicht.



  • Shade Of Mine schrieb:

    Vererbung will man eigentlich nie haben. Sie ist furchtbar.

    Das kann ich nicht nachvollziehen, konkretes Beispiel: Im DOM gibt es verschiedene Nodes, da will ich schon haben, dass Element von Node ableitet und nicht dauern mit node.type-switch-Triaden rumeiern.

    Und in diesem Zusammenhang hab ich auch ein gutes Beispiel für MI:
    Node - ChildNode (hat einen parent) - ParentNode (hat eine Liste von children) und dann gibt es eben Nodes wie Element die sowohl ChildNode als auch ParentNode sind.

    Das Diamond-Problem ist in C++ zwar etwas hässlich, aber andere Sprachen kommen damit wesentlich besser klar - Eiffel bspw.

    Ich hab mit Interesse den StackOverflow-Artikel aus dem anderen Thread gelesen der das Problem des physikalischen Speicherlayouts behandelt, das ist zwar ein Punkt, aber MI soll ja auch nicht oft vorkommen, aber immer dann wenn es notwendig ist fehlt sie mir doch.

    Jetzt ist es so, dass ParentNode von ChildNode ableitet um Code-Duplikation zu verhindern, was ziemlich, ziemlich übel ist imho. Vor allem weil nicht jeder ParentNode auch ein ChildNode ist und in manchen Ableitungen überprüft werden muss ob man eh keinen Parent hat, etc. etc.

    Welche Alternativen habe ich in dem Fall? Wie kann ich die Klassen besser darstellen und möglichst Code-Duplikation verhindern, eine sinnvolle Klassenhierarchie haben (kein ParentNode : ChildNode) und trotzdem alles auch als Node verwenden zu können?

    Dinge die ich in objektorientierten Sprachen verbieten würde: protected Member.

    @Shade: Mixins sind sehr interessant, in unserer Firma werden die im C#-Bereich sehr viel eingesetzt und sie sind sehr praktisch. Aber wie du schon gesagt hast, da muss sich imho noch etwas tun bis es da den "the way to go"-WEg gibt.

    MfG SideWinder



  • Shade Of Mine schrieb:

    Nein. Schreibarbeit sparen soll dir deine IDE, nicht Vererbung.

    Unabhängig von der Vererbung sehe ich das nicht so. Ich will mich minimal ausdrücken können, es ist schön und gut, dass mir Eclipse die Getter/Setter in Java erstellt, aber ich hasse Java trotzdem dafür, dass ich mir damit meine Klasse zumüllen muss. Die Lesbarkeit verschlechtert sich furchtbar. Ich will C#-Properties.

    C# ist deswegen eine so viel bessere Sprache (Achtung: Sprache, nicht unbedingt Plattform), weil ich mich wesentlich besser ausdrücken kann.

    MfG SideWinder



  • SideWinder schrieb:

    Unabhängig von der Vererbung sehe ich das nicht so.

    Du wuerdest doch nie auf die idee kommen properties fuer normale Funktionen zu verwenden, weil es weniger Schreibarbeit ist, oder?

    class Foo {
      public void writeFunction(string text) { System.Console.WriteLine(text);
      //vs
      public string writeProperty { set { System.Console.WriteLine(value) } }
    }
    

    Selbes bei Sachen wie Vererbung: vererbung drueckt eine Beziehung aus. Wenn man diese Beziehung will, nimmt man vererbung. Will man sie nicht, nimmt man keine Vererbung.



  • Ich verwende Vererbung nicht deswegen um mir Schreibarbeit zu sparen. Aber ich finde nicht, dass die IDE für das Ersparen von Schreibarbeit zuständig ist sondern in erster Linie die Sprache mit ihrer Ausdruckskraft.

    MfG SideWinder


Anmelden zum Antworten