Als Java-Entwickler noch C/C++ dazulernen: Chancenlos?



  • Xin schrieb:

    Vielleicht liest Du Dir Mehrfachvererbung mal an und erzählst mir nicht, was schon meine Profs nicht kapiert haben.

    Hmm, du machst das also ungefähr so wie ich befürchtet hatte. Du benutzt zu viel Vererbung. Damit stößt du dann unnötigerweise auf das Diamond-Problem.

    class FullHDTelevisionWithoutPowerSupply 
      : public Display
    {
    public:
      FullHDTelevisionWithoutPowerSupply()
      : Display( 1920, 1080 )
      {}
    };
    
    class FullHDTelevision 
      : public FullHDTelevisionWithoutPowerSupply
      , public PowerConsumer
    {
    public:
      FullHDTelevision( int minWatts, int maxWatts )
       : PowerConsumer( minWatts, maxWatts )
      {}
    };
    
    class BluRayFullHDTelevision 
      : public FullHDTelevision
      , public BluRayPlayer
    {
    public:
      BluRayFullHDTelevision()
        : FullHDTelevision( 3, 250 )
      {
      }
    };
    
    //usw..
    

    Warum benutzt du überhaupt Vererbung? Da sind doch gar keine virtuellen Methoden.
    Ich hätte das ganz einfach mit Aggregation gemacht:

    struct FullHDTelevision
    {
    	FullHDTelevision(Display display, PowerConsumer consumer);
    	const Display &getDisplay() const;
    	const PowerConsumer &getConsumer() const;
    
    private:
    
    	Display display;
    	PowerConsumer consumer;
    };
    
    struct BluRayPlayer
    {
    	explicit BluRayPlayer(PowerConsumer consumer);
    	const PowerConsumer &getConsumer() const;
    	bool isOpen() const;
    
    private:
    
    	PowerConsumer consumer;
    	bool isOpen;
    };
    
    struct BluRayTVCombination
    {
    	BluRayTVCombination(FullHDTelevision tv, BluRayPlayer player);
    	//...
    
    private:
    
    	FullHDTelevision tv;
    	BluRayPlayer player;
    };
    

    Du solltest vielleicht noch einmal dein Verständnis von ist ein überdenken. Oft meint man nämlich in Wirklichkeit eher besteht aus oder kennt.

    Mal ein Beispiel ohne Fernseher:

    struct Burger
    {
    	std::vector<BurgerLayer> layers;
    };
    
    struct PommesFrites
    {
    	bool large;
    	bool withMajo;
    };
    
    struct Drink
    {
    	enum class Type
    	{
    		Coke,
    		Sprite,
    		Water
    	};
    
    	Type type;
    	bool large;
    };
    
    struct MenuA
    {
    	Burger burger;
    	PommesFrites pommes;
    	Drink drink;
    };
    
    struct MenuB : Burger, PommesFrites, Drink
    {
    };
    

    Würdest du MenuA oder MenuB implementieren?



  • TyRoXx schrieb:

    Xin schrieb:

    Vielleicht liest Du Dir Mehrfachvererbung mal an und erzählst mir nicht, was schon meine Profs nicht kapiert haben.

    Hmm, du machst das also ungefähr so wie ich befürchtet hatte. Du benutzt zu viel Vererbung. Damit stößt du dann unnötigerweise auf das Diamond-Problem.

    Schau es Dir den Link nochmal an... Da ist kein Diamant. Du hast das Falsche kopiert.
    Und was Du unter "zu viel" verstehst ist subjektiv.

    TyRoXx schrieb:

    Warum benutzt du überhaupt Vererbung? Da sind doch gar keine virtuellen Methoden.

    Weil ich nicht nachimplementieren und nichts nicht weiterleiten muss und das Gerät sofort fertig beschrieben habe. Wer weniger programmiert macht auch weniger Fehler.

    TyRoXx schrieb:

    Ich hätte das ganz einfach mit Aggregation gemacht:

    Und schöne überflüssige getBla() Funktionen implementiert.

    TyRoXx schrieb:

    Du solltest vielleicht noch einmal dein Verständnis von ist ein überdenken. Oft meint man nämlich in Wirklichkeit eher besteht aus oder kennt.

    Ich denke, mein Verständnis von "ist ein" passt schon. Ein Fernseher mit eingebauten BluRay-Player ist ein Fernseher.

    TyRoXx schrieb:

    Mal ein Beispiel ohne Fernseher: ...

    Würdest du MenuA oder MenuB implementieren?

    Ich würde keins von beiden implementieren, ich implementiere keine Lebensmittel.

    Semantisch besteht ein Menü aus den Komponenten. Also würde ich MenuA bevorzugen. Ich habe mit MenuB aber kein Problem. Erzählst Du Deinen Freunden, dass Du den Burger aus dem Sparmenü und die Fritten aus dem Sparmenü gegessen hast und dann die Cola aus dem Sparmenü getrunken hast oder hast Du Dir einfach das Sparmenü reingezogen?

    Ich würde das also davon abhängig machen, ob ich die Komponenten einzeln betrachten möchte oder ein neues Konstrukt haben möchte. Fernseher mit Blurayplayer => Neues Konstrukt in einem Gehäuse und nicht eine Kiste, wo ich zwei Geräte reinlege.
    Ich würde also wieder MenüA bevorzugen, weil das Menü weiterhin aus drei getrennten Komponenten in einer Tüte besteht und nicht komplett in den Mixer gegeben wurden und damit ein Konstrukt entstanden ist.

    Die Mehrfachvererbung erlaubt mir diesen semantischen Unterscheid zu dokumentieren und auch anzuwenden: Ich kann einen Film auf einem Display sehen, auf einem Fernseher oder einem BluRay-TV-Kombigerät. Wenn Du großen Wert darauf legst zu betonen, dass Du den Film nicht auf dem Rahmen vom Fernseher siehst, dann solltest Du tatsächlich auf die Ableitungen verzichten und jede Menge Getter() implementieren.

    Was Du als "zuviel" bezeichnest ist subjektiv. Ich entwickle praktische Klassen, die Probleme lösen. Ich gucke Filme auf BluRay-FullHD-Kombi-Gerätes - fertig. Du schaust dann halt auf dem Display, dass Du per Getter aus dem FullHDFernsehers geholt hast, den Du per Getter aus dem BluRay-FullHD-Kombi-Gerätes geholt hast. Interessiert mich das?
    Diese Betonung muss einen Wert haben. Hat sie keinen Wert, ist es Boilerplate, das bedeutet Mehraufwand beim Schreiben und beim Lesen des Quelltextes und zusätzlich bedeutet mehr Quelltext auch mehr Chancen, Fehler einzubauen.

    Vielleicht benutzt Du auch einfach zu wenig Mehrfachvererbung. Alles subjektiv.



  • In meinen Programmen kommt nur sehr selten Vererbung vor, Mehrfachvererbung habe ich noch nie benutzt. Ein gutes Beispiel waere angebracht.



  • knivil schrieb:

    In meinen Programmen kommt nur sehr selten Vererbung vor, Mehrfachvererbung habe ich noch nie benutzt. Ein gutes Beispiel waere angebracht.

    Was ein gutes Beispiel ist, ist letztendlich wieder willkürlich. Man kann alles mit Aggregation machen. Wem das besser gefällt... Mehrfachvererbung hilft mir deutlich dabei, Typen schärfer zu differenzieren und ich spielte durchaus schon mit dem Gedanken, Aggregationen komplett abzuschaffen: weniger Boilerplate und zwangsläufig scharfe Typdefinitionen. Das will man doch!?

    Wer "ist ein" und "besteht aus" heilig spricht, darf das gerne tun, möge aber nicht damit argumentieren, dass sein Professor das gesagt hat, sondern die Gründe im Einzelfall auch benennen können. Wenn ich was aus dem Studium gelernt habe, dann dass man Professoren nicht dogmatisch folgen sollte, sondern sie als "Inspiration" verstehen sollte, einen Weg nach vorne zu finden.

    Ich bevorzuge bei manchen Dingen Funktionalität schnell neu zusammenführen zu können und fertig.
    Sehr häufig passiert das, wenn ich ein Objekt mit unterschiedlichen Konzepten ausstatten möchte. Nehmen wir an, ich möchte ein Bildbearbeitungsprogramm schreiben:

    class BitmapDocument : public Node < BitmapDocument >
                           , public Filename
                           , Bitmap
    {};
    

    Kann man alles aggregativ machen, aber ein Bitmapdokument ist auch eine Node, eine Repräsentation einer Datei und eine Bitmap. Eben wie ein technischer Bauplan, auf dem unten rechts diverse Angaben aufgedruckt sind und der Rest des Papiers zum Zeichnen da ist und an der Seite zwei Löcher, um ihn abzuheften. Das ist ein neues Konstrukt und nicht mehr nur eine Zeichnung, trotzdem kann man auf dem Bauplan zeichnen.
    Ich baue mir noch einen Konstruktor dazu und bin fertig.

    Sehr schön, wenn man Sachen ändert - ich muss weniger Getter ändern, keine Member.

    Ich nutze durchaus auch Mehrfachvererbung, um mehrfach von Node<> abzuleiten, wenn ich Objekte in mehreren Listen verwalten möchte - so könnte Bitmap ebenfalls von Node< Bitmap > abgeleitet sein, falls ich davon eine eigene Liste pflegen würde - eben eine List<Bitmap>, die sich ganz eindeutig gegenüber einer List<BitmapDocument> unterscheidet.

    Ich habe kein Problem damit, wenn Leute anders programmieren, weil man es ihnen anders beigebracht hat. Ich beschäftige mich seit über 10 Jahren mit Sprachdesign und habe inzwischen eine eigene Schlachtbank für heilige Kühe zu Hause.
    Mir hat man auch viel beibebracht, ich besitze die Scott Meyers Bücher beide und habe sie auch gelesen. Es sind hervorragende Bücher. Aber wir stellen heute auch die Bibel in Frage und genauso stelle ich ein "ist ein"-Dogma in Frage. Für mich stellt sich nicht die Frage, ob Scott Meyers oder ein anderer Prof das gesagt hat, sondern nur "Hat das ganze für mich einen praktischen Nutzen?"
    Und die Strategien, die Java/C# hier verfolgen, halte ich für ausgemachten Blödsinn.

    Ich bin heute absolut überzeugt, dass die Mehrfachvererbung wieder kommt. Dafür steckt viel zuviel Potential darin, Boilerplate zu sparen und den Entwickler dazu zu nötigen, sich eindeutig auszudrücken. Ein vollkommen unterschätztes Konzept.



  • Xin schrieb:

    Ich bin heute absolut überzeugt, dass die Mehrfachvererbung wieder kommt. Dafür steckt viel zuviel Potential darin, Boilerplate zu sparen und den Entwickler dazu zu nötigen, sich eindeutig auszudrücken. Ein vollkommen unterschätztes Konzept.

    🤡 🤡 🤡



  • Ich bin heute absolut überzeugt, dass die Mehrfachvererbung wieder kommt. Dafür steckt viel zuviel Potential darin, Boilerplate zu sparen und den Entwickler dazu zu nötigen, sich eindeutig auszudrücken. Ein vollkommen unterschätztes Konzept.

    Aber sicher nicht wie C++ das macht.
    Sondern eher mit traits ala Scala.



  • Xin schrieb:

    ich spielte durchaus schon mit dem Gedanken, Aggregationen komplett abzuschaffen: weniger Boilerplate und zwangsläufig scharfe Typdefinitionen. Das will man doch!?

    Wenn ich richtig verstanden habe, wie du Vererbung einsetzt, koppelst du aber auch stärker. Stichwort als Beispiel: Inversion of Control.
    Außerdem kannst du Kapselung schwieriger steuern, bei Aggregation mit Komponente K kannst du die Implementierung für Aggregationspartner leicht verstecken und dabei für Subklassen von K immernoch gewisse Teile des Zustands zugreifbar machen [protected].



  • marco.b schrieb:

    Xin schrieb:

    ich spielte durchaus schon mit dem Gedanken, Aggregationen komplett abzuschaffen: weniger Boilerplate und zwangsläufig scharfe Typdefinitionen. Das will man doch!?

    Wenn ich richtig verstanden habe, wie du Vererbung einsetzt, koppelst du aber auch stärker. Stichwort als Beispiel: Inversion of Control.

    Wieso kopple ich stärker? Wenn ich beschreibe, dass ein Kleiderbügel ein Ausführung eines Hakens mit Kleiderhalter ist und eine Ausführung von Kleiderhalter ist - oder ob ein Kleiderbügel aus einen Haken und einem Kleiderhalter besteht, so habe ich so oder so einen Kleiderbügel.

    Das hat keinen Einfluss darauf, wie die der Haken mit der Kleiderstange kommuniziert oder der Kleiderhalter mit der Kleidung!?

    Oder verstehe ich Deinen Punkt hier gerade nicht korrekt?

    marco.b schrieb:

    Außerdem kannst du Kapselung schwieriger steuern, bei Aggregation mit Komponente K kannst du die Implementierung für Aggregationspartner leicht verstecken und dabei für Subklassen von K immernoch gewisse Teile des Zustands zugreifbar machen [protected].

    Wie gesagt, ich habe auch kein Problem mit Aggregationen, ich nutze die Mehrfachvererbung nur gerne, als öffentliche Aggregation mit vereinfachtem Zugriff. Vererbung ist nichts anderes als Aggregation.
    Man schreibt seine Klassen entsprechend so, dass man gut von ihnen ableiten kann und arbeitet wie mit Lego.
    Finde ich sehr praktisch, spart Code und das ist in meinen Augen auch semantisch zu vertreten, denn "besteht aus" und "ist ein" ist häufig nur die Frage, wie man sich der Sache nähert.

    Die Farbe eines Autos, ist ein öffentliches Attribut. Mein Auto ist keine Farbe. Trotzdem ist mein Auto blau (myCar == Color::Blue;). Ich kann auch sagen, die Farbe von meinem Auto ist blau (myCar.Color == Color::Blue;). Die Frage ist schlussendlich, wieviel Kontext brauchst Du?
    Bei einer Funktion Color::Mix( yourCar, myCar ) muss man selbst wissen, ob man erwähnen muss, dass man die Farbe meint? Wenn das undurchschaubar ist, geht das über Aggregation natürlich deutlicher: Color::Mix( yourCar.Color, myCar.Color )

    Dein Punkt ist absolut korrekt, wenn es darum geht, Daten zu schützen, zum Beispiel das FileHandle in einer class File.

    Allerdings interessieren sich Funktionen, die z.B. nur mit einem PowerSupply arbeiten nicht dafür, was das reingereichte Objekt denn noch so alles kann. Ich sehe nicht, was ich hier verstecken müsste, vielleicht ein BluRay-Player, vielleicht ein Fernseher, egal... ich frage nur, wieviel Strom das Ding verbraucht: ganz öffentliche Funktion.

    Ich halte Mehrfachvererbung für ein sehr praktisches Mittel, Quellcodes kurz zu halten und mache reichlich Gebrauch davon und kann das Dogma, dass Mehrfachvererbung Werk des Teufels wäre, absolut nicht nachvollziehen.



  • Xin schrieb:

    Ich gucke Filme auf BluRay-FullHD-Kombi-Gerätes - fertig. Du schaust dann halt auf dem Display, dass Du per Getter aus dem FullHDFernsehers geholt hast, den Du per Getter aus dem BluRay-FullHD-Kombi-Gerätes geholt hast. Interessiert mich das?

    Ich finde die ganze Klassenhierachie eh unglücklich, aber um mal bei dem Beispiel zu bleiben:
    Wie implementierst du denn allgemeine Funktionen, die auf der Basisklasse arbeiten? Sowas wie

    bool canPlayBlueRay(Display* display)
    

    Mit dynamic_cast?



  • Jockelx schrieb:

    Xin schrieb:

    Ich gucke Filme auf BluRay-FullHD-Kombi-Gerätes - fertig. Du schaust dann halt auf dem Display, dass Du per Getter aus dem FullHDFernsehers geholt hast, den Du per Getter aus dem BluRay-FullHD-Kombi-Gerätes geholt hast. Interessiert mich das?

    Ich finde die ganze Klassenhierachie eh unglücklich, aber um mal bei dem Beispiel zu bleiben:
    Wie implementierst du denn allgemeine Funktionen, die auf der Basisklasse arbeiten? Sowas wie

    bool canPlayBlueRay(Display* display)
    

    Mit dynamic_cast?

    Gar nicht. Die Frage hat auch nix mit Mehrfachvererbung zu tun.

    Ein Display kann keine Blurays abspielen. Darum ist es ein Display. Das Display selbst hat auch keine Verwandtschaft mit einem BluRayPlayer. Die Antwort wäre also grundsätzlich 'false'. Wenn ich die Antwort kenne, ist die Frage überflüssig.

    Eine Funktion, die auf der Basisklasse arbeitet und sich für BluRay-Funktionalität interessiert würde als Parameter einen BluRayPlayer erwarten und der würde "bool canPlayBlueRay(BluRayPlayer * player)" grundsätzlich true antworten und - hatten wir ja schon: Wenn ich die Antwort kenne, ist die Frage überflüssig.

    Was immer ich mit canPlayBlueRay() reinstecken kann - sagen wir mal this - ist von irgendeinem Typen, der mindestens von Display geerbt hat.

    Deine Frage wäre mit dynamic_cast zu beantworten, aber nicht schön zu lesen.
    Wenn ich diese Frage also stellen möchte, dann muss ich mich (also this) fragen, ob ich BluRays abspielen kann.

    Beispiel aus meiner Praxis: Ich programmiere eine eigene Programmiersprache, dort gibt es Operatoren (z.B. Addition oder Negierung). Hier stellt sich zum Beispiel unter Umständen die Frage, ob das ein binärer Operator ist.

    Die Vererbungs Hierarchie ist dann Operator->PriorityOperator->BinaryOperator->Addition
    Operator definiert die Frage virtual bool IsBinary() const { return false; }, BinaryOperator überschreibt die Funktion mit { return true; }.

    Wenn Du das nicht von Hand ändern möchtest, dann wäre der dynamic_cast das Mittel der Wahl - würde aber zur Laufzeit mehr Zeit in Anspruch nehmen.

    bool IsBinary() const { return nullptr != dynamic< BinaryOperator const * >( this ); }
    

    Aber wie gesagt - das ist eine gerade Vererbungslinie: Das hat nichts mit Displays zu tun oder Mehrfachvererbung zu tun.
    Du müsstest Du dem Display die Fragemöglichkeit mitgeben, ob es BluRays abspielen kann und diese Information - vollkommen unabhängig von der Mehrfachvererbung an der Stelle ändern, wo Du per Mehrfachvererbung den BlurayPlayer dazupackst.
    In dem Fall musst Du ohne Mehrfachvererbung sogar auf die return true/false-Lösung ausweichen, da Du zur Laufzeit nicht mehr prüfen kannst, ob Deine Instanz eine Aggregation BluRayPlayer besitzt und selbst dann wäre das übergebene Display nur das Display und Du hättest mit dem (Display *)-Datentyp keine Chance mehr an den BluRay-Player ran zu kommen, der eventuell in dem Objekt sitzt, in dem das Display als Member enthalten ist.

    Entsprechend wirst Du Deine Funktionen so formulieren, dass Du diese Probleme gar nicht hast, womit wir wieder bei der ersten Antwort auf Deine Frage sind: Eine Frage wie "bool canPlayBlueRay(Display* display)" muss ich nicht implementieren und wenn doch habe ich etwas verkehrt gemacht, was nichts mit Mehrfachvererbung zu tun.

    Hier holst Du Dir über dynamic_cast nur die Möglichkeit rein, eine Frage, zu beantworten, die Du Dir hoffentlich gar nicht stellen musst.



  • Was genau ist der Sinn davon, ein Document von Filename erben zu lassen!?

    Xin schrieb:

    Beispiel aus meiner Praxis: Ich programmiere eine eigene Programmiersprache, dort gibt es Operatoren (z.B. Addition oder Negierung). Hier stellt sich zum Beispiel unter Umständen die Frage, ob das ein binärer Operator ist.

    Was hat die Basisklasse Operator für einen Sinn, wenn du später dann solche Fragen stellst?

    Xin schrieb:

    Vererbung ist nichts anderes als Aggregation.

    Wieso kann ich ein Objekt von abgeleitetem Typ dann wie ein Objekt vom Typ der Basisklasse behandeln? Das widerspricht doch dem Grundgedanken von Aggregation!?



  • dot schrieb:

    Was genau ist der Sinn davon, ein Dokument von Filename erben zu lassen!?

    Das ist so nie geschrieben worden.

    BitmapDocument erbte von Document, Filename und Bitmap.

    Es ist auch nur ein Beispiel. Ich habe zwar die jeweiligen Klassen, aber afair keine Klasse BitmapDocument. Was ich damit fabriziert habe, hätte einen eigenen Artikel zur Erklärung benötigt, also habe ich das nur flott auf BitmapDocument runtergebrochen.

    PS: Und der Rest:

    dot schrieb:

    Xin schrieb:

    Beispiel aus meiner Praxis: Ich programmiere eine eigene Programmiersprache, dort gibt es Operatoren (z.B. Addition oder Negierung). Hier stellt sich zum Beispiel unter Umständen die Frage, ob das ein binärer Operator ist.

    Was hat die Basisklasse Operator für einen Sinn, wenn du später dann solche Fragen stellst?

    Funktionalität hochziehen, die nicht in den einzelnen Operatoren ausgeführt werden muss. Die Frage stellt sich, wenn ein Algorithmus in class Operator für eine bestimmte Sorte von Operatoren anders funktioniert, wenn es ich um einen Binären Operator handelt.

    dot schrieb:

    Xin schrieb:

    Vererbung ist nichts anderes als Aggregation.

    Wieso kann ich ein Objekt von abgeleitetem Typ dann wie ein Objekt vom Typ der Basisklasse behandeln? Das widerspricht doch dem Grundgedanken von Aggregation!?

    Das hängt sehr stark davon ab, was Du unter Aggregation verstehst.

    Aggregation ist eine "Haufen von Zeugs" und sagt nix darüber wer hier welche Rechte hat.

    Bei:

    struct A
    {
      int a;
    };
    
    struct B : public A
    {
      int b;
    } bi;
    
    struct C
    {
      A a;
      int b;
    } ci;
    

    unterscheiden sich class B und class C überhaupt nicht. Du darfst die Instanz bi überall da reinwerfen, wo Du A reinwerfen darfst und bei C musst Du halt ci.a schreiben.

    Am Programm ändert das nichts - es stellt sich nur die Frage, ob Du das betonen möchtest oder nicht.

    Eine Aggregation ist beides: die Anhäufung der beiden Membern a und b, jeweils 4 Byte groß. Du kannst lustig hin- und hercasten. Knorr oder Maggie - alles Tütensuppe.



  • Xin schrieb:

    dot schrieb:

    Was genau ist der Sinn davon, ein Dokument von Filename erben zu lassen!?

    Das ist so nie geschrieben worden.

    BitmapDocument erbte von Document, Filename und Bitmap.

    Erklär mir bitte, wie das sein kann, dass ein BitmapDocument, das von Document, Filename und Bitmap erbt, nicht von Filename erbt!?

    Xin schrieb:

    dot schrieb:

    Xin schrieb:

    Beispiel aus meiner Praxis: Ich programmiere eine eigene Programmiersprache, dort gibt es Operatoren (z.B. Addition oder Negierung). Hier stellt sich zum Beispiel unter Umständen die Frage, ob das ein binärer Operator ist.

    Was hat die Basisklasse Operator für einen Sinn, wenn du später dann solche Fragen stellst?

    Funktionalität hochziehen, die nicht in den einzelnen Operatoren ausgeführt werden muss. Die Frage stellt sich, wenn ein Algorithmus in class Operator für eine bestimmte Sorte von Operatoren anders funktioniert, wenn es ich um einen Binären Operator handelt.

    Wenn ein Algorithmus für eine bestimmte Sorte von Operatoren anders funktioniert, dann bedeutet das, dass er nicht für alle Operatoren gleich funktioniert. Damit stellt sich unweigerlich die Frage, wieso das Interface des Algorithmus das Gegenteil behauptet, indem es jeden beliebigen Operator entgegen nimmt. (Siehe auch: http://en.wikipedia.org/wiki/Liskov_substitution_principle)

    Xin schrieb:

    Bei:

    [...]

    unterscheiden sich class B und class C überhaupt nicht. Du darfst die Instanz bi überall da reinwerfen, wo Du A reinwerfen darfst und bei C musst Du halt ci.a schreiben.

    Das ist nicht das gleiche und funktioniert überhaupt nur, weil C::a public ist.

    Wenn du tatsächlich der Meinung bist, dass Vererbung und Aggregation äquivalent sind, dann erklär mir, basierend auf dieser Annahme, bitte den Output von folgendem Programm:

    #include <iostream>
    
    struct A 
    { 
    }; 
    
    struct B : public A 
    { 
      int b; 
    }; 
    
    struct C 
    { 
      A a; 
      int b; 
    };
    
    int main()
    {
      std::cout << sizeof(A) << '\n' << sizeof(B) << '\n' << sizeof(C) << '\n';
    }
    

    Output (MSVC 11 x64):

    1
    4
    8
    


  • Na das hat doch jetzt eher was mit Optimierung leerer Basisklassen zu tun. Trotzdem ist fuer mich Vererbung der Ausnahmefall und der eingeschlagene Weg von Xin fuer falsch. Es gibt genug Argumente und Beispiele im Internet, rueckblickend finde ich meinen Verzicht auf Vererbung als gut.



  • dot schrieb:

    Xin schrieb:

    dot schrieb:

    Was genau ist der Sinn davon, ein Dokument von Filename erben zu lassen!?

    Das ist so nie geschrieben worden.

    BitmapDocument erbte von Document, Filename und Bitmap.

    Erklär mir bitte, wie das sein kann, dass ein BitmapDocument, das von Document, Filename und Bitmap erbt, nicht von Filename erbt!?

    Keine Ahnung wie das sein sollte, wie ich schon schrieb: BitmapDocument erbte von Filenamen.

    Document und Filename sind Basisklassen, die grundlegende Konzepte anbieten, BitmapDocument ist die Arbeitsklasse, die diese dann zusammenführt und nutzt.
    Ich denke, Du verwechselst hier Document und BitmapDocument. Daher auch die Erklärung auf die Frage, die Du vermutlich stellen wolltest.

    Alles andere ist bereits erklärt und ich sehe keinen Grund, hier in die Endlosschleife zu gehen.

    dot schrieb:

    Xin schrieb:

    Funktionalität hochziehen, die nicht in den einzelnen Operatoren ausgeführt werden muss. Die Frage stellt sich, wenn ein Algorithmus in class Operator für eine bestimmte Sorte von Operatoren anders funktioniert, wenn es ich um einen Binären Operator handelt.

    Wenn ein Algorithmus für eine bestimmte Sorte von Operatoren anders funktioniert, dann bedeutet das, dass er nicht für alle Operatoren gleich funktioniert. Damit stellt sich unweigerlich die Frage, wieso das Interface des Algorithmus das Gegenteil behauptet, indem es jeden beliebigen Operator entgegen nimmt...

    Weil es eben nicht nur ein Interface ist, sondern eine Ableitung. Das ist ja gerade der Vorteil der Mehrfachvererbung zum Interface.
    Das bedeutet eben, dass Operator Algorithmen anbieten kann, die entsprechend des Interfaces von Operator etwas ausrechnen, was für jeden beliebigen Operator funktioniert.

    dot schrieb:

    Xin schrieb:

    Bei:

    [...]

    unterscheiden sich class B und class C überhaupt nicht. Du darfst die Instanz bi überall da reinwerfen, wo Du A reinwerfen darfst und bei C musst Du halt ci.a schreiben.

    Das ist nicht das gleiche und funktioniert überhaupt nur, weil C::a public ist.

    Ui, eine Tatsachenbehauptung! Das deutet erstmal deutlich auf eine Meinungsverschiedenheit hin. Wie überraschend.

    Wäre es nicht public, wäre es eine andere Semantik. Es funktioniert nicht "nur weil", sondern "genau deswegen, weil" es public ist. Ganz andere Perspektive.

    Dies hier führt in eine lange, langweilige Diskussion über den jeweiligen Vergleichsoperation, die mit dem Ergebnis endet, dass Du sagst, dass man das so nicht machen darf und ich frage Dich, wer das Dogma aufgestellt hat und warum mich das interessieren soll und so geht das hin und her und wir landen wieder bei dem wichtigen Punkt, den Du nicht mitzitiert hast: Der Abstimmung auf einen gemeinsam akzeptierte Vergleichsoperation:

    Xin schrieb:

    Das hängt sehr stark davon ab, was Du unter Aggregation verstehst.

    Aggregation ist eine "Haufen von Zeugs" und sagt nix darüber wer hier welche Rechte hat.

    Also sparen wir uns das böse Blut, am Ende tun wir eh, was wir jeweils für richtig halten.

    Ich muss nachher sowieso langsam mal den ganzen Kram hier raussortieren, denn ich befürchte, dem armen Threadstarter haben wir schon längst irrtümlich und fälschlich vermittelt, dass es chancenlos ist, sinnvoll C++ zu lernen, wenn nichtmals C++-Entwickler sich über Grundlagen einig werden. 😉



  • knivil schrieb:

    Na das hat doch jetzt eher was mit Optimierung leerer Basisklassen zu tun. Trotzdem ist fuer mich Vererbung der Ausnahmefall und der eingeschlagene Weg von Xin fuer falsch. Es gibt genug Argumente und Beispiele im Internet, rueckblickend finde ich meinen Verzicht auf Vererbung als gut.

    Obgleich dieses Verhalten gemeinhin als "Empty Base Optimization" bekannt ist, handelt es sich dabei imo um viel mehr als eine Optimierung. Die Tatsache, dass diese Optimierung möglich ist, illustriert genau den fundamentalen Unterschied zwischen Vererbung und Aggregation.

    Ein Objekt ist etwas, das von anderen Objekten unterschieden werden kann, d.h. es ist eindeutig identifizierbar.
    Ein Objekt der Klasse A muss eine Größe > 0 haben, da dies sonst bedeuten würde, dass mehr als ein Objekt den selben Platz einnehmen könnte und die Objekte nichtmehr unterscheidbar wären. Gleichermaßen muss das Subobjekt a eines Objektes der Klasse C eindeutig identifizierbar sein, da es sich ja um ein von einer Instanz von C verschiedenes Objekt handelt. Jedes Objekt der Klasse B ist dagegen allerdings auch ein Objekt der Klasse A und kein separates Objekt...

    Xin schrieb:

    dot schrieb:

    Xin schrieb:

    dot schrieb:

    Was genau ist der Sinn davon, ein Dokument von Filename erben zu lassen!?

    Das ist so nie geschrieben worden.

    BitmapDocument erbte von Document, Filename und Bitmap.

    Erklär mir bitte, wie das sein kann, dass ein BitmapDocument, das von Document, Filename und Bitmap erbt, nicht von Filename erbt!?

    Keine Ahnung wie das sein sollte, wie ich schon schrieb: BitmapDocument erbte von Filenamen.

    Document und Filename sind Basisklassen, die grundlegende Konzepte anbieten, BitmapDocument ist die Arbeitsklasse, die diese dann zusammenführt und nutzt.
    Ich denke, Du verwechselst hier Document und BitmapDocument. Daher auch die Erklärung auf die Frage, die Du vermutlich stellen wolltest.

    Alles andere ist bereits erklärt und ich sehe keinen Grund, hier in die Endlosschleife zu gehen.

    Stimmt, sry, ich wollte eigentlich nach dem Sinn fragen, ein Dokument von Filename abzuleiten und dachte dabei konkret an das BitmapDocument.



  • dot schrieb:

    knivil schrieb:

    Na das hat doch jetzt eher was mit Optimierung leerer Basisklassen zu tun. Trotzdem ist fuer mich Vererbung der Ausnahmefall und der eingeschlagene Weg von Xin fuer falsch. Es gibt genug Argumente und Beispiele im Internet, rueckblickend finde ich meinen Verzicht auf Vererbung als gut.

    Das ist eben nicht einfach nur eine Optimierung, sondern zeigt genau den fundamentalen Unterschied auf.

    Ein Objekt ist etwas, das von anderen Objekten unterschieden werden kann, d.h. es ist eindeutig identifizierbar. Ein Objekt der Klasse A muss eine Größe > 0 haben, da dies sonst bedeuten würde, dass mehr als ein Objekt den selben Platz einnehmen könnte und die Objekte nichtmehr unterscheidbar wären. Gleichermaßen muss das Subobjekt a eines Objektes der Klasse C eindeutig identifizierbar sein, da es sich ja um ein von einer Instanz von C verschiedenes Objekt handelt. Jedes Objekt der Klasse B ist dagegen allerdings auch ein Objekt der Klasse A und kein separates Objekt...

    Ich mag Deine Argumentation, weil sie zeigt, dass hier eine technischer Unterschied vorliegt. Es ist aber ein Spezialfall: das leere Objekt, hier wird ein Trick verwandt, um die Identifizierbarkeit zu gewährleisten.

    Sicher, ein Sonderfall reicht, um seine Argumentation zu begründen, doch hier ist der Sonderfall vor allem eine willkürliche Entscheidung des VC für den Sonderfall, was wie knivil richtig sagt.

    knivil schrieb:

    Na das hat doch jetzt eher was mit Optimierung leerer Basisklassen zu tun.

    Würde sich VC nämlich auf eine einheitliche Darstellung leerer Klassen in allen drei Fällen einigen, was durchaus sinnvoll wäre, dann wäre der VC auch im Sonderfall kein Argument für Deine argumentative Darstellung.

    knivil schrieb:

    Trotzdem ist fuer mich Vererbung der Ausnahmefall und der eingeschlagene Weg von Xin fuer falsch. Es gibt genug Argumente und Beispiele im Internet, rueckblickend finde ich meinen Verzicht auf Vererbung als gut.

    Damit hat keiner ein Problem. Genauso halte ich mein Herangehen für vorteilhaft und baue das weiter aus.
    Allerdings wäre ich dankbar, wenn Du mir diese Argumente als PM zur Verfügung stellen könntest, da ich meine Programmiersprache sehr stark auf Vererbung aufbaue und gerne überprüfe, ob ich etwas übersehen habe.

    Sollte das also nicht nur eine argumentenverstärkende aber leere Worthülse gewesen sein, wäre ich für einen konstruktiven Beitrag dankbar.



  • Xin schrieb:

    Ich mag Deine Argumentation, weil sie zeigt, dass hier eine technischer Unterschied vorliegt. Es ist aber ein Spezialfall: das leere Objekt, hier wird ein Trick verwandt, um die Identifizierbarkeit zu gewährleisten.

    Sicher, ein Sonderfall reicht, um seine Argumentation zu begründen, doch hier ist der Sonderfall vor allem eine willkürliche Entscheidung des VC für den Sonderfall, was wie knivil richtig sagt.

    Es ist eben nicht einfach nur ein technischer Unterschied, sondern ein prinzipieller und der Spezialfall sollte als Illustration dienen. Der Punkt ist nicht, dass ein bestimmter Compiler hier ein anderes Layout generiert (was jeder brauchbare moderne Compiler tun wird). Der Punkt ist, dass ein Compiler hier ein anderes Layout generieren kann, eben weil es sich um zwei rein konzeptionell fundamental verschiedene Dinge handelt. Vererbung modelliert eine "ist-ein" Beziehung (B ist ein A) und Aggregation eine "hat-ein" Beziehung (C hat ein A) und das sind eben zwei völlig orthogonale Konzepte (ein Auto hat vier Räder, aber es ist kein Rad vs. ein Schiff ist ein Fahrzeug aber es hat keine Räder)...



  • dot schrieb:

    Es ist eben nicht einfach nur ein technischer Unterschied, sondern ein prinzipieller und der Spezialfall sollte als Illustration dienen. Der Punkt ist nicht, dass ein bestimmter Compiler hier ein anderes Layout generiert (was jeder brauchbare moderne Compiler tun wird).

    Er kann den Unterschied so generieren, dass er für Deine Argumentation nicht passt.
    Die beiden Argumentationslinien sind orthogonal -> das eine eignet sich nicht um das andere zu begründen. Es trifft sich nur zufälligerweise in dem einen Punkt, den Du aufgezeigt hast.

    dot schrieb:

    Der Punkt ist, dass ein Compiler hier ein anderes Layout generieren kann, eben weil es sich um zwei rein konzeptionell fundamental verschiedene Dinge handelt. Vererbung modelliert eine "ist-ein" Beziehung (B ist ein A) und Aggregation eine "hat-ein" Beziehung (C hat ein A) und das sind eben zwei völlig verschiedene Konzepte (ein Auto hat vier Räder, aber es ist kein Rad vs. ein Schiff ist ein Fahrzeug aber es hat keine Räder)...

    Jaja, hatten wir alles schon.

    Du hast das Dogma gut verinnerlicht. Ein Fahrrad hat zwei Räder. Ein Fahrrad ist ein Zweirad. Ein Motorrad ist ein Zweirad.
    Wieviele Räder hat eigentlich ein Zweirad? Natürlich keine, da ein Fahrrad zwei Räder hat und ein Zweirad ist. Hätte ein Zweirad zwei Räder, hätte ein Fahrrad ja vier...
    Hmm... jetzt wird's wieder kompliziert...

    Alles eine Frage der Perspektive. Du darfst Perspektiven wechseln und das auch modellieren. Am Schluss sollten aber am Fahrrad wie auch Zweirad genau zwei Räder sein. Ob es jetzt zwei Räder hat oder ein Zweirad ist, ist dabei Geschmackssache. Falsch ist aber keins davon, denn es kommt das gleiche raus, nur bei vier Rädern ist was schief gelaufen.

    Interessant wird es nur, wenn das Fahrrad ein Zweirad ist, das heimlich ein drittes Rad versteckt hat. Dann "hat es" ein privates Rad... oder ist ein DrittesRadVerstecker... man weiß es nicht 😉



  • Neil deGrasse Tyson schrieb:

    The good thing about science is that it's true whether or not you believe in it.


Anmelden zum Antworten