protected-/private-Vererbung



  • Ich dachte immer. protected ist dafür da, dass nur die Klasse selbst und die abgeleitete Klassen zugreifen können, public um auch Nutzer der Klasse (inkl. davon abgeleiteter Klassen) zugreifen zu lassen und private nur die Klasse selbst zugreifen kann (nicht mal die abgeleiteten Klassen dürfen).
    Hab ich da was falsch verstanden?🤔


  • Mod

    @Helmut-Jakoby sagte in protected-/private-Vererbung:

    Ich dachte immer. protected ist dafür da, dass nur die Klasse selbst und die abgeleitete Klassen zugreifen können, public um auch Nutzer der Klasse (inkl. davon abgeleiteter Klassen) zugreifen zu lassen und private nur die Klasse selbst zugreifen kann (nicht mal die abgeleiteten Klassen dürfen).
    Hab ich da was falsch verstanden?🤔

    Doch ganz richtig. Das "Puzzle" war hier ja, was passiert, wenn man protected-Member dann auch noch protected weitervererbt, und dann noch in einer abgeleiteten Memberfunktion ein Objekt der Basisklasse hat. Die Antwort ist auch wohldefiniert (und ergibt nach der internen Logik auch Sinn), aber wenigstens für C++Sepp war die Antwort unerwartet.

    Der Rest der Diskussion hat mit der Frage eigentlich gar nichts mehr zu tun, da geht es darum, ob das Konzept grundsätzlich eine gute Idee ist oder nicht.


  • Mod

    @SeppJ Würdest Du zustimmen, dass das Konzept von Kapselung entlang eines Spektrums verlaufen kann? Ist es sinnvoll ein Cluster im Zentrum dieses Spektrums in der Sprache verfügbar zu machen? Was genau ist dein Wunsch, protected aus der Sprache zu streichen?

    Das Argument gegen protected ist daher kein grundsätzliches sprachtheoretisches Argument

    Aber Dein Argument führt uns dialektisch immer noch auf die theoretische Seite, weil Du ja an die Integrität von Kapselung appellierst, anstatt mit konkreten Beispielen oder Anti-Beispielen zu argumentieren.

    .., sondern dass viele Leute sehr positive Erfahrungen mit dem Modell der objektorientierten Programmierung haben, und möchten, dass man dieses Modell möglichst konsequent durchzieht.

    Ist es nicht etwas borniert, einem Sprachfeature die Daseinsberechtigung abzusprechen, weil man ein puristisches Bild von OOP durchpeitschen will? Komfortabilitaet und Einfachheit sind in der Entwicklung massgebend (und werden unweigerlich einer Sprache helfen, sich durchzusetzen, was man nicht unterschätzen darf).

    Aber eine normale Klasse, mit einer normalen Klassenvariable, die normaler Teil des State ist, da ist protected ein deutlicher Code Smell.

    Ich stimme zu, aber: Das ist weniger ein Argument gegen protected sondern mehr ein Argument dafür, protected spärlich einzusetzen. Nur weil die berechtigte Domäne klein ist, ist sie nicht leer.

    Es gibt jetzt natürlich auch andere, sehr erfolgreiche, Sprachen, wo es gar kein Konzept von access specifiern gibt, wo trotzdem stringente, objektorientierte Software drin geschrieben wird. Da gilt dann halt, dass alle Beteiligten "consenting adults" sein müssen, und sich im Klaren sind, was wie angefasst werden darf, und was nicht. Wenn du protected benutzt, wirfst du die Hilfsmechanismen der Sprache weg, und musst dich darauf verlassen, dass du nur mit solchen verantwortungsvollen Leuten zu tun hast. Dann kannst du es auch gleich public machen, so wie in Sprachen ohne access specifier.

    Interessant, welche Sprachen hattest Du da im Sinn? Python ist IIRC keine Sprache die in der Praxis in wirklich grossen Projekten mit komplexen Hierarchien und Gliederungen eingesetzt wird.


  • Mod

    @Columbo sagte in protected-/private-Vererbung:

    @SeppJ Würdest Du zustimmen, dass das Konzept von Kapselung entlang eines Spektrums verlaufen kann? Ist es sinnvoll ein Cluster im Zentrum dieses Spektrums in der Sprache verfügbar zu machen? Was genau ist dein Wunsch, protected aus der Sprache zu streichen?

    Dass, wenn jemand protected in seinem Programm schreibt, darüber nachgedacht wird, warum man das macht. Wenn man das konsequent macht, wird man in den meisten Fällen darauf kommen, dass es falsches Design ist. Das Loggerbeispiel ist gut, denn ich hatte den Eindruck, dass hustbaer sich nicht bewusst war, warum das hier ok ist (denn dann hätte er es erklärt und nicht einfach nur das Beispiel gegeben). Es ist aus den von mir gegebenen Gründen ok, nachdem ich gründlich darüber nachgedacht habe.

    Sieh es ähnlich an wie einen reinterpret_cast. Ziemlich exakt die gleichen Argumente, und da wird es als selbstverständlich angesehen, dass das in den allermeisten Fällen ein Zeichen für schlechtes Design ist, und die (tatsächlich existierenden) Ausnahmen gehören jeweils genau begründet.

    Interessant, welche Sprachen hattest Du da im Sinn? Python ist IIRC keine Sprache die in der Praxis in wirklich grossen Projekten mit komplexen Hierarchien und Gliederungen eingesetzt wird.

    C betritt den Raum.


  • Mod

    @SeppJ sagte in protected-/private-Vererbung:

    Interessant, welche Sprachen hattest Du da im Sinn? Python ist IIRC keine Sprache die in der Praxis in wirklich grossen Projekten mit komplexen Hierarchien und Gliederungen eingesetzt wird.

    C betritt den Raum.

    " [..] wo trotzdem stringente, objektorientierte Software drin geschrieben wird" 😬 😂



  • @SeppJ sagte in protected-/private-Vererbung:

    Das Loggerbeispiel ist gut, denn ich hatte den Eindruck, dass hustbaer sich nicht bewusst war, warum das hier ok ist (denn dann hätte er es erklärt und nicht einfach nur das Beispiel gegeben). Es ist aus den von mir gegebenen Gründen ok, nachdem ich gründlich darüber nachgedacht habe.

    Ich hab die mMn. relevanten Punkte erklärt. Aber auch bewusst ein Beispiel ausgewählt bei dem ich davon ausgegangen bin dass sich die meisten damit anfreunden können. Dass das const da wichtig ist, war mir klar.

    Was dein Argument angeht...

    Der Logger ist höchstwahrscheinlich keine harte Invariante der Klasse,

    Das ist mMn. nicht unbedingt notwendig. protected kann denke ich in manchen Fällen auch Sinn machen wenn die abgeleitete Klasse die Invarianten der Basisklasse zerstören könnte. Kann dir jetzt aus dem Stegreif kein gutes Beispiel dafür geben, aber mein Bauchgefühl sagt dass es solche Fälle gibt. Speziell wenn es auf Performance ankommt.

    Dass man das normalerweise vermeiden sollte, ist auch klar.

    Wenn du protected benutzt, wirfst du die Hilfsmechanismen der Sprache weg, und musst dich darauf verlassen, dass du nur mit solchen verantwortungsvollen Leuten zu tun hast. Dann kannst du es auch gleich public machen, so wie in Sprachen ohne access specifier.

    Nope.
    protected schützt dich immer noch davor dass beliebiger Code an den Sachen rumfummeln kann. Der Knackpunkt ist dabei gerade dass du in einer abgeleiteten Klasse eben nicht sowas machen kannst:

    class BadActor : public MyBase {
    public:
        static void setFoo(MyBase* b, int value) {
            b->foo = value; // nicht erlaubt wenn MyBase::foo protected ist
        }
    };
    

    (Natürlich kann man hier mit einem static_cast schummeln, aber ich denke das ist dann laut Standard UB wenn das Objekt auf das b zeigt in Wirklichkeit gar kein BadActor ist.)

    D.h. wenn du irgendwo ein MyDerived Objekt erzeugst, und einem anderen Programmteil z.B. einen Zeiger darauf übergibst, dann schützt dich protected davor dass dieser andere Programmteil irgendwie an den protected Teilen deines Objekts rumschraubt.

    Was man natürlich auch ohne böse Casts machen kann, ist eine eigene abgeleitete Klasse zu definieren, die Unfug mit den protected Membern anstellt, ein Objekt davon erzeugen - und dann halt Unfug damit anstellen. Aber man kann eben keinen Unsinn mit Instanzen von anderen Klassen anstellen die von MyBase abgeleitet sind.

    Oder anders gesagt: protected schützt Objekte, so lange sie keine Sub-Objekte von Klassen sind die sich nicht an die Regeln halten und so lange man keine verbotenen Casts verwendet.

    Und daher finde ich die Aussage "dann kannst du es auch gleich public machen" unpassened.



  • @SeppJ sagte in protected-/private-Vererbung:

    Der Logger fällt hier ein bisschen aus dem Rahmen. Er ist ja nicht wirklich Teil des funktionalen Zustands der Klasse. Wenn man wirklich konsequent objektorientiert denkt, dürfte er gar kein Teil der Klasse selbst sein, sondern müsste eigentlich in einem globalen Loggermapping leben, dass einer Klasse ihren zuständigen Logger bereit hält (so wie das beispielsweise in Python auch tatsächlich gemacht wird). Dass er überhaupt in der Klasse lebt, ist ein Zugeständnis an die Bequemlichkeit, wo man kontrolliert ausnahmsweise die strikte Objektorientierung verletzen darf.

    Das sehe ich ganz anders. Ich halte globale Logger/Logger-Mappings/LogManager/... für einen Code-Smell. Unterschiedliche Programmteile könnten unterschiedliche Logger/LogManager/... für Objekte des selben Typs verwenden wollen. In automatisierten Tests wird das dann auch sehr schnell lästig wenn man das nicht kann. Zumindest sofern man Tests parallel im selben Prozess ausführen will.

    Man weiß ja, dass die Sichtbarkeit des Loggers die Kapselung nicht verletzt, da er kein echter Teil des State ist.

    Die Information darüber wo Log-Messages hinzugehen haben ist mMn. schon ein Teil des States.

    Da können dann auch solche Mechnismen wie protected das richtige Mittel zum Zweck sein. Aber eine normale Klasse, mit einer normalen Klassenvariable, die normaler Teil des State ist, da ist protected ein deutlicher Code Smell.

    Was wenn die Variable const ist aber aus Gründen der Kapselung trotzdem nicht einfach public sein soll?
    Was wenn es keine Variable sondern Memberfunktionen sind?



  • @SeppJ sagte in protected-/private-Vererbung:

    Das "Puzzle" war hier ja, was passiert, wenn man protected-Member dann auch noch protected weitervererbt, und dann noch in einer abgeleiteten Memberfunktion ein Objekt der Basisklasse hat.

    Dass die protected Member nochmal protected weitervererbt werden, ist hierbei egal. Funktioniert auch nicht wenn die Basisklasse public geerbt wird.

    class Base {
    protected:
        int p;
    };
    
    class Derived : public Base {};
    
    class OtherDerived : public Base {
    public:
        void foo() {
            Base b;
            b.p = 123; // error: 'int Base::p' is protected within this context
            Derived d;
            d.p = 123; // error: 'int Base::p' is protected within this context
        }
    
        static void foo(Base* b) {
            b->p = 123; // error: 'int Base::p' is protected within this context
        }
    
        static void foo(Derived* d) {
            d->p = 123; // error: 'int Base::p' is protected within this context
        }
    };
    


  • Welche Frage sich jetzt noch stellt: Wenn alle Elemente aus den Post vom 25.04.2022 private anstatt protected vererbt werden und die selben Variablen einen Fehler/gar keinen Fehler liefern( im ausgeklammerten Teil des Codes zu sehen) , welchen Sinn hat dann eigentlich die private gegenüber der protected-Vererbung...abgesehen von den Regeln aus den
    Post vom 26.04.2022 von Helmut.Jakoby? Danke!



  • Wenn du des englischen mächtig bist, dann lies dir mal den detailierten Artikel C++ Tutorial Private Inheritance - 2020 durch.
    Kurz gesagt, geht es bei der privaten Vererbung nicht um eine "ist ein"-Beziehung, sondern um eine "hat ein"-Beziehung (also Komposition). Es dient technisch also nur dem Zweck auf protected Member zugreifen zu können.
    Man kann also auch sagen, es stellt eigentlich gar keine Vererbung dar.


Anmelden zum Antworten