"to MI or not to MI"



  • 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



  • SideWinder schrieb:

    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.

    Ich weiss nicht ob ich das Unterschreiben wuerde.
    Klar ist es praktisch wenn ich mit wenigen Zeichen komplexe Sachen ausdruecken kann - aber worum es geht, ist das was ich Ausdruecke.

    Oft will ich ja viel Schreiben, zB bei Funktionsnamen.

    Schreibarbeit sollte IMHO nie ein Argument sein. zB so Sachen wie Access Specifiers wie private/public sind mehr Schreibarbeit als alles public zu machen.

    Auch sind manche Sprachen generell geschwaetziger als andere.

    Wenn also ein Konzept wie Properties Schreibarbeit erspart ist das nett - aber es sollte nie die Motivation fuer die Verwendung so eines Konzeptes sein.



  • Mhm, ich bin nicht in der Lage mich richtig auszudrücken. Ich meine natürlich mit "Schreibarbeit sparen" nicht Variablennamen i,j,k,l,m,n, etc., die darf mir natürlich die IDE mit CTRL+SPACE verlängern.

    Es geht mir in erster Linie um Boilerplate-Code der meine Klasse vergrößert, verkomplexisiert. Eben 10 Getter statt Properties. Eine 5-Zeilen-For-Schleife die Elemente filtert anstatt result = collection.where(.Value > 10). Solcherlei.

    Die For-Schleife muss sich jemand erst ansehen, nachsehen ob sie eh kein +=2 im Index hat, ob sie eh nicht irgendwo etwas ein klein wenig anders macht. Weil ich nicht ausdrücken kann was ich eigentlich sagen mag: Gib mir nur jene Elemente deren Value größer als 10 ist.

    Edit: Darum geht's hier aber nicht. Würde mich eher freuen wenn jemand etwas zu meinem Posting über MI schreibt 🤡

    MfG SideWinder



  • 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

    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.

    Sorry, für dieses eher sinnlose Posting, ich habe den Thread hier gerade erst nach einem Hinweis von Sidewinder gesehen. Durch RudP cruise ich nur bei zuviel Zeit und ich kann jetzt nicht drei Seiten durchhangeln...

    Kellerautomat schrieb:

    Ich bitte darum, die Diskussion konstruktiv fortzusetzen und nicht wieder gegenseitig aufeinander rumzuhacken - das fuehrt zu nichts.

    ...mag mich aber auch keiner Diskussion entziehen, die offen und konstruktiv werden soll. Ich hoffe heute abend nochmal Zeit zu finden und klinke mich dann nochmal ein.



  • SideWinder schrieb:

    [...]

    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.
    [...]

    MfG SideWinder

    Was genau hat man denn dann eigentlich gewonnen? Nun sind die Objekte zwar in sich klar strukturiert, aber der Code, welcher ein fertiges DOM vorgesetzt bekommt (und das ist ja der einzig relevante Code, für den man die Mühe betreibt, alles klar, leserlich und strukturiert zu machen, als Bibliotheksschreiber), weiß doch immer noch nicht die konkreten Typen und muss trotzdem irgendwie Type-Switching betreiben. Oder sehe ich da was falsch?

    Auf meiner Todo-Liste steht auch bald wieder eine einfache Baumstruktur und ich habe leider noch keine gute Lösung für das Inner-Node/Leaf-Problem gefunden. Sollte jeder innere Knoten eventuell zwei getrennte Listen für weitere untergeordnete innere Knoten bzw. Blatt-Knoten enthalten? Sollte er sie in einer Liste vorhalten und als Implementierungsdetail den Type-Switch für den Client-Code durchführen?

    Edit: Bliebe natürlich noch, die Typ-Auflösung per Double-Dispatch zu betreiben. Irgendwie habe ich das aber noch nicht zu schätzen gelernt, weiß auch nicht warum.



  • @Decimad: Hmm, ich bin einfach schlecht im Ausdrücken heute, aber anders gesagt: Wenn ich keine gemeinsame Basisklasse habe, kann ich im ParentNode nicht einmal eine List<ChildNode> halten die mir auch gleich autom. garantiert, dass ich nicht einen Non-ChildNode Child als einhängen kann.

    MfG SideWinder


Anmelden zum Antworten