Ableitungen virtual



  • manni66 schrieb:

    Nö, funktioniert wunderbar:

    Nur weil es technisch funktioniert, heisst es nicht dass das liskov prinzip nicht doch verletzt wurde.



  • #include <string>
    #include <iostream>
    
    struct A
    {
    private:
        virtual std::string f() { return "A"; }
    };
    
    struct B : public A
    {
      virtual std::string f() { return "B"; }
    };
    
    void print( A& aA )
    {
            std::cout << aA.f() << std::endl;
    }
    
    int main()
    {
        A aA;
        B aB;
    
        print( aA );
        print( aB );
        return 0;
    }
    

    So gehts aber nicht @manni66



  • Shade Of Mine schrieb:

    manni66 schrieb:

    Nö, funktioniert wunderbar:

    Nur weil es technisch funktioniert, heisst es nicht dass das liskov prinzip nicht doch verletzt wurde.

    Das stimmt, das Prinzip wurde aber nicht verletzt: ich kann den Subtyp B überall als A einsetzen und das Programm funktioniert korrekt.



  • Dweb schrieb:

    #include <string>
    #include <iostream>
    
    struct A
    {
    private:
        virtual std::string f() { return "A"; }
    };
    
    struct B : public A
    {
      virtual std::string f() { return "B"; }
    };
    
    void print( A& aA )
    {
            std::cout << aA.f() << std::endl;
    }
    
    int main()
    {
        A aA;
        B aB;
    
        print( aA );
        print( aB );
        return 0;
    }
    

    So gehts aber nicht @manni66

    1. das habe ich auch nicht behauptet
    2. auch hier wird die is-a-Beziehung aufrecht erhalten: was für A nicht geht funktioniert weiterhin für B nicht


  • Belli schrieb:

    ConfusedGuy schrieb:

    ja kann man du solltest(kannst?) sie nur nicht einschränken. da sonst die is-a-beziehung kaputt ist.

    Ach, man kann doch sogar die ganze Klasse gleich private/protected ableiten.

    Kann man durchaus, allerdings bedeutet private Vererbung keine is-a-Beziehung, sondern die Übernahme von Implementationsdetails und ist hauptsächlich im Zusammenhang mit empty-base-optimization von Interesse.

    Es ist wichtig, zu bemerken, dass is-a im objektorientierten Sinne nicht das gleiche bedeutet wie im allgemeinen Sprachgebrauch. "A is-a B" bedeutet, dass A alles kann, was B auch kann und man eine Instanz von A dementsprechend auch als Instanz von B benutzen kann. Insbesondere bedeutet das, dass zusätzliche Einschränkungen in abgeleiteten Klassen im objektorientierten Sinne nicht passieren - beispielsweise ist ein Rechteck hier ein Quadrat, aber ein Quadrat nicht notwendigerweise ein Rechteck, und ein tief religiöser Mensch ist objektorientiert ein Atheist, der zusätzlich halt an Götter glaubt.

    Erbe ich eine Klasse privat, so ist diese Beziehung nicht vorhanden.

    struct A {};
    struct B : private A {};
    
    int main() {
      B b;
      A &r = b; // KAWUMM!
    }
    

    funktioniert nicht, dementsprechend gibt es zwischen B und A keine is-a-Beziehung.



  • Okay ...
    wenn ich aber einzelne Einschränkungen treffe, dann funktioniert es:

    class basis
    {
    	public:
    		void foo(){int a = 0;}
    };
    
    class erbt : public basis
    {
    	private:
    		void foo();
    };
    
    int main()
    {
    	erbt objekt;
    
    	basis& objektRef = objekt;
    
    	objektRef.foo();  //geht
    
    	//objekt.foo();   Dies geht nicht!
    }
    

    Ansonsten entnehme ich Deinen Ausführungen, daß Vererbung nicht grundsätzlich eine "ist ein(e)" - Beziehung ausdrückt, sondern nur, wenn man in der abgeleiteten Klasse keinerlei Einschränkungen bzgl. der geerbten Attribute vornimmt?



  • Nö, wieso? Du schränkst ja nichts ein - ich kann ein Objekt vom Typ erbt nach wie vor als Objekt vom Typ basis benutzen. Dass du halbärschig versuchst, Methoden zu verstecken, hat darauf keine Auswirkung - du machst lediglich die Notation etwas merkwürdig.

    Ich habe den Eindruck, du zäumst das Pferd von hinten auf. Häng dich nicht an Sprachmitteln auf - nicht die Sprache ist objektorientiert, sondern dein Programmmodell. Du entwickelst ein objektorientiertes Modell, dann implementierst du es mit den Mitteln deiner Sprache - und üblicherweise ist der beste Weg, in C++ eine is-a-Beziehung auszudrücken, die öffentliche Vererbung.

    Wenn du stattdessen auf das in C übliche Pointer-Punning zurückgreifst, wird dein Programm unhandlicher, aber nicht weniger objektorientiert, und es ist durchaus möglich, Typen, die in keiner Weise eine is-a-Beziehung haben, durch öffentliche Vererbung miteinander zu verbinden; dein Code wird dadurch unangenehm zu benutzen, aber machbar ist es durchaus. Man könnte beispielsweise

    struct cow {
      virtual void eat(double kilograms);
      virtual void moo() const;
    };
    
    struct dog : cow {
      virtual void bark() const;
    private:
      virtual void moo() const; // >.<
    }
    

    schreiben und in der Dokumentation erwähnen, dass dog nicht als cow benutzt werden sollte, aber es wäre fehleranfälliger als ein vernünftiges, objektorientiertes Modell (und jeder andere Programmierer, der deinen Code in die Finger bekäme, schlüge die Hände über dem Kopf zusammen).

    Per Konvention erwartet man, dass durch öffentliche Vererbung verbundene Typen eine is-a-Beziehung haben, während private Vererbung die Übernahme von Implementationsdetails bedeutet. Du musst dich an diese Konvention nicht halten, aber es ist in aller Regel eine gute Idee, es zu tun.



  • manni66 schrieb:

    Das stimmt, das Prinzip wurde aber nicht verletzt: ich kann den Subtyp B überall als A einsetzen und das Programm funktioniert korrekt.

    Es mag technisch funktionieren, aber du verletzt idR damit invarianten da es einen Grund gibt warum die Funktion nicht public ist.

    Wenn wir von schlechtem Design ausgehen und es keinen Grund gibt warum die Funktion protected ist - dann mag es uU OK sein sie public zu setzen.

    Wobei ich gerne soweit gehe und Liskov hier rauslasse und sage: sowas tut man einfach nicht.



  • seldon schrieb:

    Es ist wichtig, zu bemerken, dass is-a im objektorientierten Sinne nicht das gleiche bedeutet wie im allgemeinen Sprachgebrauch.

    Das sehe ich Anders. Öffentliche Vererbung sollte nur dann eingesetzt werden wenn man auch im normalen Sprachgebrauch sagen kann: <Kindelement> ist ein <Parentelement> (z.B. Angestellter ist eine Person), und zudem das Kindelement auch ohne Einschränkungen als Parentelement eingesetzt werden kann (z.B. mag ein Quadrat zwar ein Rechteck sein, aber nur über Einschränkungen des Verhaltens).



  • Shade Of Mine schrieb:

    manni66 schrieb:

    Das stimmt, das Prinzip wurde aber nicht verletzt: ich kann den Subtyp B überall als A einsetzen und das Programm funktioniert korrekt.

    Es mag technisch funktionieren, aber du verletzt idR damit invarianten da es einen Grund gibt warum die Funktion nicht public ist.

    Wenn wir von schlechtem Design ausgehen und es keinen Grund gibt warum die Funktion protected ist - dann mag es uU OK sein sie public zu setzen.

    Wobei ich gerne soweit gehe und Liskov hier rauslasse und sage: sowas tut man einfach nicht.

    "Weil man das nicht tut" hat mir schon als Kind nicht als Begründung für etwas ausgereicht 😉

    Wenn die Regel lautet "das sollte man gut begründen" kann ich dem zustimmen, aber "sowas tut man nicht" sagt man nicht 😉



  • asc schrieb:

    Das sehe ich Anders. Öffentliche Vererbung sollte nur dann eingesetzt werden wenn man auch im normalen Sprachgebrauch sagen kann: <Kindelement> ist ein <Parentelement> (z.B. Angestellter ist eine Person), und zudem das Kindelement auch ohne Einschränkungen als Parentelement eingesetzt werden kann (z.B. mag ein Quadrat zwar ein Rechteck sein, aber nur über Einschränkungen des Verhaltens).

    Naja, wenn wir erstmal von Lehrbuchbeispielen wegkommen, ist der allgemeine Sprachgebrauch sowieso nicht mehr anwendbar. "Ein basic_ios ist ein ios_base" hat außerhalb der Programmierung keine Bedeutung.

    Der Punkt ist, dass im allgemeinen Sprachgebrauch "ist ein" im relevanten Sinne eine Ähnlichkeit mit kleinen Modifikationen am Konzept bedeutet, die nicht alle in den objektorientierten Gebrauch passen - dort ist entscheidend, dass eine abgeleitete Klasse die Funktionalität der Basisklasse erweitert. Das deckt sich gelegentlich mit dem allgemeinen Sprachgebrauch, kann aber auch gegenläufig sein. Es kann sich sogar gleichzeitig decken und gegenläufig sein, denn der allgemeine Sprachgebrauch ist oft keinesfalls eindeutig.

    Und noch aus einem anderen Grund ist der allgemeine Sprachgebrauch eine schlechte Metrik für die Qualität eines objektorientierten Modells: wenn du eine Klasse "Angestellter" programmierst, die von "Person" erbt, implementierst du weder eine komplette Person noch einen kompletten Angestellten, sondern nur die Aspekte der beiden, die du für dein Modell brauchst. Je nachdem, welche Aspekte das sind, erhält man völlig andere Verwandschaftsstrukturen.

    Beispiel: Ich modelliere das Tierreich. Taxonomisch betrachtet könnte ich etwa so vorgehen:

    class animal   { };
    
    class dinosaur : public animal { };
    class mammal   : public animal { };
    
    class bird     : public dinosaur { };
    class bat      : public mammal   { };
    class monkey   : public mammal   { };
    class human    : public monkey   { };
    

    interessiert mich aber nicht die Taxonomie, sondern beispielsweise die Fortbewegungsfähigkeit, komme ich eher auf etwas in dieser Art:

    class animal { };
    
    class   flying_animal : public animal { };
    class climbing_animal : public animal { };
    class  walking_animal : public animal { };
    
    class bird   : public   flying_animal { };
    class bat    : public   flying_animal { };
    class monkey : public climbing_animal { };
    class human  : public  walking_animal { };
    

    Der allgemeine Sprachgebrauch bringt mich da nicht weiter. Zumal, möchte ich anmerken, es trotz biologischer Tatsachen im allgemeinen Sprachgebrauch nach wie vor unüblich ist, Menschen als Affen oder gar Tiere zu bezeichnen.



  • seldon schrieb:

    Der Punkt ist, dass im allgemeinen Sprachgebrauch "ist ein" im relevanten Sinne eine Ähnlichkeit mit kleinen Modifikationen am Konzept bedeutet, die nicht alle in den objektorientierten Gebrauch passen

    Ganz davon abgesehen das Ableitungen grundsätzlich mit Vorsicht zu genießen sind, und deutlich zu häufig Verwendung finden.

    seldon schrieb:

    interessiert mich aber nicht die Taxonomie, sondern beispielsweise die Fortbewegungsfähigkeit, komme ich eher auf etwas in dieser Art:

    Und ich komme spätestens hier an einen Punkt, wo man Komposition der Ableitung vorziehen sollte (was ohnehin häufig der bessere Weg ist, um starke Kopplungen zu vermeiden) - es sei den es geht um rein virtuelle Schnittstellen (in anderen Sprachen Inferface genannt).

    Eine Ausnahme sehe ich auch hier, in speziellen Gebrauch mit C++: "Konfigurierbare" Klassen (Policy based Design...).

    Allgemein gesprochen gibt es meines Erachtens kaum eine schlechtere Wahl für ein Klassendesign als tiefe Vererbungshierarchien, auch und insbesondere in den von dir gezeigten Antibeispiel (Wie bildest du hier sinnvoll fliegende Wesen, die gleichzeitig gut schwimmen können etc. nach). Komposition ist sehr häufig eine bessere Alternative.


Anmelden zum Antworten