protected-/private-Vererbung



  • Hey Leute,

    ich habe, um die protected- & private-Vererbung genauer zu verstehen, zwei Klassen mit verschiedenen Membervariablen angelegt, wobei XData proctected als private erbt. In der Memfunktion test werden alle Membervariablen für beide "Vererbungsarten" auf verschiedene Art und Weise aufgerufen, wobei man genau sieht, was funktioniert und was nicht:

    class Data
    {
        public:
            Data() = default;
    
            int testv1_b_pu;
    
        protected:
            int testv3_b_ptc;
    
        private:
            int testv2_b_pr;
    };
    
    class XData : protected Data
    {
        protected:
            int testv4_d_ptc;
    
        private:
            int testv5_d_pr;
    
        public:
            XData() = default;
            int test6_d_pu;
    
            void test()
            {
                //protected-Vererbung:
                testv1_b_pu = 1;
                testv2_b_pr = 1; // Fehler wie erwartet
                testv3_b_ptc = 1;
                testv4_d_ptc = 1;
                testv5_d_pr = 1;
    
                Data dobj;
                dobj.testv1_b_pu = 2;
                dobj.testv2_b_pr = 2; // Fehler
                dobj.testv3_b_ptc = 2; // !
                dobj.testv5_d_pr = 2; // Fehler wie erwartet
                XData xobj;
                xobj.testv1_b_pu = 3;
                xobj.testv2_b_pr = 3; // Fehler wie erwartet
                xobj.testv3_b_ptc = 3; // ! warum hier kein Fehler?
                xobj.testv4_d_ptc = 3;
                xobj.testv5_d_pr = 3;
    
                //private-Verbung:
                /*testv1_b_pu = 1;
                testv2_b_pr = 1; // Fehler wie erwartet
                testv3_b_ptc = 1;
                testv4_d_ptc = 1;
                testv5_d_pr = 1;
    
                Data dobj;
                dobj.testv1_b_pu = 2;
                dobj.testv2_b_pr = 2; // Fehler
                dobj.testv3_b_ptc = 2; // !
                dobj.testv5_d_pr = 2; // Fehler wie erwartet
                XData xobj;
                xobj.testv1_b_pu = 3;
                xobj.testv2_b_pr = 3; // Fehler wie erwartet
                xobj.testv3_b_ptc = 3; // ! warum hier kein Fehler?
                xobj.testv4_d_ptc = 3;
                xobj.testv5_d_pr = 3;*/
            }      
    };
    

    Ich verstehe nach wie vor nicht, warum beim Auftruf der protected-Variablen "dobj.testv3_b_ptc" über ein Objekt der
    Basisklasse kein Fehler auftritt, beim Aufruf über ein Objekt xobj der abgeleiteten hingegen schon.
    Eigentlich werden bei der protected-Verberbung ja alle public- und protected-Elemente der Basisklasse zu protected-Elementen in der abgeleiteten Klasse. Der Zugriff auf protected-Element ist für Methoden und friend-Funktionen der abgeleiteten Klasse erlaubt. Für Objekte der Basisklasse und abgeleiteten laut hingegen Standard nicht erlaubt.
    Demzufolge müsste bei beiden Aufrufen doch der Fehler kommen. Warmu nicht? Vielen Dank!



  • @C-Sepp sagte in protected-/private-Vererbung:

    testv3_b_ptc = 1;
    dobj.testv3_b_ptc = 2; // !
    xobj.testv3_b_ptc = 3; // ! warum hier kein Fehler?
    

    Ich verstehe nach wie vor nicht, warum beim Auftruf der protected-Variablen "dobj.testv3_b_ptc" über ein Objekt der
    Basisklasse kein Fehler auftritt, beim Aufruf über ein Objekt xobj der abgeleiteten hingegen schon.
    ...
    Der Zugriff auf protected-Element ist für Methoden und friend-Funktionen der abgeleiteten Klasse erlaubt. Für Objekte der Basisklasse und abgeleiteten laut hingegen Standard nicht erlaubt.

    Irgendwie passt Dein Text nicht zu den Kommentaren in Deinem Code ...
    Vielleicht solltest Du den Code mal auf die Zeilen kürzen, um die es dann im Text auch gehen soll ...



  • Hier das Wesentliche etwas übersichtlicher dargestellt:

    {
        public:
            Data() = default;
    
            int testv1_b_pu;
    
        protected:
            int testv3_b_ptc;
    
        private:
            int testv2_b_pr;
    };
    
    class XData : protected Data
    {
        protected:
            int testv4_d_ptc;
    
        private:
            int testv5_d_pr;
    
        public:
            XData() = default;
            int test6_d_pu;
    
            void test()
            {
                //protected-Vererbung:          
                Data dobj;
                dobj.testv1_b_pu = 2;
                dobj.testv2_b_pr = 2; // Fehler
    
                dobj.testv3_b_ptc = 2; // ! hier Fehler
    
                dobj.testv5_d_pr = 2; // Fehler wie erwartet
                XData xobj;
                xobj.testv1_b_pu = 3;
                xobj.testv2_b_pr = 3; // Fehler wie erwartet
    
                xobj.testv3_b_ptc = 3; // ! warum hier kein Fehler?
    
                xobj.testv4_d_ptc = 3;
                xobj.testv5_d_pr = 3;           
            }      
    };
    

    Entschuldige, es muss natürlich heißen:
    "Ich verstehe nach wie vor nicht, warum beim Auftruf der protected-Variablen "xobj.testv3_b_ptc" über ein Objekt der
    abgeleiteten Klasse kein Fehler auftritt, beim Aufruf über ein Objekt dobj der Basisklasse hingegen schon.


  • Mod

    test ist eine Memberfunktion von XData. Ein Data ist aber kein XData, daher hat test keine speziellen Privilegien um auf Membervariablen von Data zuzugreifen. Dazu braucht es ein XData-Objekt, da darf test auf die geschützten Member zugreifen. Was genau das ist, was du beobachtest.

    Du hast gerade einen der Gründe entdeckt, warum protected (sowohl für Sichtbarkeit als auch für Vererbung) ziemlicher Murks ist und eigentlich niemals benutzt wird, außerhalb von Übungsaufgaben und äußerst konstruierten Beispielen.



  • Ich habe noch nie protected Vererbung in der Realität gesehen in ~13 Jahren.



  • Eine gute Erklärung war für mich in: https://de.wikibooks.org/wiki/C%2B%2B-Programmierung:_Vererbung

    Zitat:

    Zugriffskontrolle
    
    Die Vererbungsart zeigt an, ob beim Vererben der Zugriff auf Elemente der Basisklasse eingeschränkt wird.
    Sie wird vor dem Namen der Basisklasse angegeben.
    Wie bei Memberdeklarationen gibt es die Schlüsselwörter public, protected und private (Standard-Vererbungsart).
    
    Die Deklarationen
    
    class A { /* ... */ };
    class B : public A    { /* ... */ };
    class C : protected A { /* ... */ };
    class D : private A   { /* ... */ }; // (oder (Standard-Vererbungsart): "class D : A { /* ... */ };")
    
    bewirken folgendes:
    
    Ist ein Element in A       public 	protected 	private
    ... wird es in B 	   public 	protected 	nicht übergeben
    ... wird es in C 	   protected 	protected 	nicht übergeben
    ... wird es in D 	   private 	private 	nicht übergeben
    
    Beachten Sie, dass friend-Beziehungen nicht vererbt werden.
    

    Hab ich aber auch noch nicht gemacht, obwohl es ggf. mal Sinnvoll war um z.B. so was wie final hinzubekommen.



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

    Du hast gerade einen der Gründe entdeckt, warum protected (sowohl für Sichtbarkeit als auch für Vererbung) ziemlicher Murks ist und eigentlich niemals benutzt wird, außerhalb von Übungsaufgaben und äußerst konstruierten Beispielen.

    Ich verstehe das Argument/die Kritik nicht. Was soll daran Murks sein?

    Murks wäre es wenn der XData Code grundsätzlich immer auf protected Member jedes Data Objekts zugreifen könnte. Denn dann wäre protected effektiv public. Man müsste sich bloss eine Hilfsklasse bauen die statische Memberfunktionen für den Zugriff anbietet, und schon hätte jeder der möchte von Überall aus Zugriff auf die protected Teile. Das wäre schlecht, und deswegen ist es nicht so.

    Was Murks ist, ist die übertriebene Verwendung von Vererbung die man oft in Lehrmaterial bzw. leider auch realen Programmen findet. Dort wo Vererbung allerdings wirklich Sinn macht, ist protected ein recht nützliches Tool.

    Konkretes Beispiel aus der Praxis: wir haben oft protected: shared_ptr<Logger> const m_logger Member in unseren Klassen. Die Basisklasse braucht den Logger, die abgeleitete Klasse ebenso. Dafür zwei getrennte private Member zu machen ist IMO sinnfrei. Von Aussen soll der Logger aber nicht erreichbar sein, denn es soll ja schliesslich nicht jede solche Klasse Dependency-Locator für den Logger sein. Und ebenso soll ein FooService nicht auf den Logger eines BarService zugreifen können, auch wenn beide von z.B. ServiceBase ableiten welches den protected Logger-Zeiger enthält.


  • Mod

    @hustbaer sagte in protected-/private-Vererbung:

    @SeppJ sagte in protected-/private-Vererbung:

    Du hast gerade einen der Gründe entdeckt, warum protected (sowohl für Sichtbarkeit als auch für Vererbung) ziemlicher Murks ist und eigentlich niemals benutzt wird, außerhalb von Übungsaufgaben und äußerst konstruierten Beispielen.

    Ich verstehe das Argument/die Kritik nicht. Was soll daran Murks sein?

    Hier ist das Verhalten zumindest milde unintuitiv. Ist aber nicht der Hauptgrund, wieso protected ein komisches Konzept ist.

    Die Hauptgründe dafür:

    Als Zugriffsspezifizierer: Was für einen Sinn macht eine Zugriffsklasse, die eigentlich keinen Zugriff erlaubt, aber uneigentlich dann wieder doch Vollzugriff gewährt? Denn es kann ja jeder einfach davon erben. Die Klasse hat ja keine Kontrolle darüber, wer von ihr erbt. Die Zugriffsklassen haben ja den Zweck, dass die Klasse ihre Attribute wenn nötig kapseln kann, um ihre Invarianten gewährleisten zu können. Dabei kann sie entweder Vollzugriff auf ein Attribut erlauben (weil's die Invarianten dadurch nicht verletzt werden können), oder sie muss ihn verbieten (weil jede unkontrollierte Änderung eine Invariante verletzen würde), oder sie kann ihn gezielt erlauben mittels Friend (weil sie gewissen Codes vertraut, keinen Mist zu treiben, und diese beispielsweise nur lesen). Aber wann macht dann jemals eine Spezifikation Sinn, wo sie Vollzugriff gewährt für jeden, der nett fragt? Da kann das Attribut dann auch gleich public sein.

    Als Vererbungsspezifizierer: Das Verhalten ist so obskur und unnütz, dass die meisten Leute nicht einmal wissen, dass es existiert. Ich kann dir gar nicht ausführlich argumentieren, wieso. Denn es ist so obskur und unnütz, dass ich selber erst einmal gründlich nachlesen müsste, was es eigentlich genau macht. Es ist wahrscheinlich nur da, weil man private- und public-Vererbung hatte, und sich sagte, dass man aus Symmetriegründen auch noch protected-Vererbung braucht. Es soll wohl private-Vererbung mit Extras sein, aber private-Vererbung selbst fällt ja ganz oft in die Kategorie von erzwungenen Beispielen, wo fast immer Komposition die bessere Lösung wäre.

    Ich bin vielleicht wirklich zu sehr von den schlechten Lehrbuchbeispielen geschädigt, die fast immer ein "hier wäre Komposition 10x besser" sind. Dein Loggerbeispiel klingt erst einmal ganz sinnvoll, denn das hat gerade nicht die Probleme, die ich oben beschreibe. Der Logger ist höchstwahrscheinlich keine harte Invariante der Klasse, er ist auch noch const, und man will die technische Vererbungshierarchie ja gerade genau durch die Zugriffsrechte nachbilden.


  • Mod

    "protected ist komisch, deshalb sollte man es nicht benutzen" ist einfach non sequitur. (Ja ich weiss, dass das nicht Deine konkrete Aussage war, aber das ist die Aussage die beim Leser ankommt.) Es hat einen bestimmten use case der in der Praxis oft auftritt. non licet bovi wuerde ich im Rahmen eines Forums noch verstehen, aber pauschal zu erklären, protected ergebe keinen Sinn oder sollte nicht eingesetzt werden, weil es nicht so konsequent wie public/private ist, nicht.

    @SeppJ sagte in protected-/private-Vererbung:

    Als Zugriffsspezifizierer: Was für einen Sinn macht eine Zugriffsklasse, die eigentlich keinen Zugriff erlaubt, aber uneigentlich dann wieder doch Vollzugriff gewährt? Denn es kann ja jeder einfach davon erben. Die Klasse hat ja keine Kontrolle darüber, wer von ihr erbt. Die Zugriffsklassen haben ja den Zweck, dass die Klasse ihre Attribute wenn nötig kapseln kann, um ihre Invarianten gewährleisten zu können. Dabei kann sie entweder Vollzugriff auf ein Attribut erlauben (weil's die Invarianten dadurch nicht verletzt werden können), oder sie muss ihn verbieten (weil jede unkontrollierte Änderung eine Invariante verletzen würde), oder sie kann ihn gezielt erlauben mittels Friend (weil sie gewissen Codes vertraut, keinen Mist zu treiben, und diese beispielsweise nur lesen). Aber wann macht dann jemals eine Spezifikation Sinn, wo sie Vollzugriff gewährt für jeden, der nett fragt? Da kann das Attribut dann auch gleich public sein.

    Der grundsätzliche Zweck eines access specifiers liegt darin, den User davon abzuhalten, eine Abhängigkeit gegenüber Membern zu entwickeln, die nicht dem Design/der intendierten Nutzung der Klasse zugrundeliegen, sondern einfach aus Notwendigkeit in der Klasse existieren. protected erfüllt diesen Zweck nach aussen, aber erlaubt den abgeleiteten Klassen aus Notwendigkeit auf den Member zugreifen zu dürfen, und räumt ein, dass diese Abhängigkeit kein langfristiges Problem ist, weil eine nachträgliche Aenderung entweder unwahrscheinlich oder downstream leicht zu beheben ist.

    Die Frage wäre jetzt, ob dieser beschriebene Fall in der Praxis regelmäßig vorkommt, d.h. dass eine Abhängigkeit in der ableitenden Klasse sinnvoll, eine Abhängigkeit von Besitzern aber nicht sinnvoll ist. Hustbaer's Beispiel ist gar nicht schlecht: wenn jeder Besitzer anfängt, abhängig vom Logger Referenz Member zu werden, wird es deutlich mehr refactor Arbeit geben, wenn diese Klasse aus dem Kontext verschwindet, indem diese Abhängigkeit auftritt. Es ist andererseits unwahrscheinlich, dass der Logger als Member verschwindet (wie sonst soll man in der Basisklasse dann loggen), weshalb die Abhängigkeit in der abgeleiteten Klasse weniger problematisch wäre. Ich kenne auch weitere Beispiele. AFAICS ist protected also praktisch relevant. Ob es fuer Missbrauch anfällig ist, ist eine andere Frage.

    Du hast gerade einen der Gründe entdeckt, warum protected [..] eigentlich niemals benutzt wird, außerhalb von Übungsaufgaben und äußerst konstruierten Beispielen.

    Ich habe viele Codebases gesehen in denen protected gängig ist, nicht zuletzt die meiner aktuellen Firma (die, um der Skepsis vorzubeugen, aus sehr kompetenten Codern besteht). (Ich kann auch gerne Beispiele liefern.) Was fuer ein sample hast Du?

    Als Vererbungsspezifizierer: Das Verhalten ist so obskur und unnütz, dass die meisten Leute nicht einmal wissen, dass es existiert. Ich kann dir gar nicht ausführlich argumentieren, wieso. Denn es ist so obskur und unnütz, dass ich selber erst einmal gründlich nachlesen müsste, was es eigentlich genau macht. Es ist wahrscheinlich nur da, weil man private- und public-Vererbung hatte, und sich sagte, dass man aus Symmetriegründen auch noch protected-Vererbung braucht. Es soll wohl private-Vererbung mit Extras sein, aber private-Vererbung selbst fällt ja ganz oft in die Kategorie von erzwungenen Beispielen, wo fast immer Komposition die bessere Lösung wäre.

    Diesem Teil stimme ich zu.


  • Mod

    Mir ist schon klar, was man mit dem Sprachfeature erreichen möchte, aber das heißt nicht, dass das zu gutem Design führt. Eine Menge Sprachfeatures sind gut gemeint, aber gehören trotzdem vermieden.

    Die Ideale des objektorientierten Designs verlangen Kapselung, die Klasse muss Kontrolle über ihren Zustand haben, so dass sie ihre innere Funktionsweise garantieren kann. protected bricht diese Kapselung auf, auf eine Weise die für die Ursprungsklasse nicht nachvollziehbar ist, da sie nicht wissen kann, wer von ihr erbt. Wenn man also protected benutzt, verliert man also die Vorteile, die man durch die access specifier eigentlich erreichen wollte, nämlich native Sprachunterstützung für Kapselung. Das Argument gegen protected ist daher kein grundsätzliches sprachtheoretisches Argument, 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. Nutzt man protected, dann sagt man, dass man stringente objektorientierte Kapselung ablehnt. Kann man der Meinung sein, aber mehrheitlich gilt Objektorientierung ja schon als bewährtes, architektonisches Ideal, das sich in der Praxis bewiesen hat.

    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.

    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. Man weiß ja, dass die Sichtbarkeit des Loggers die Kapselung nicht verletzt, da er kein echter Teil des State ist. 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.



  • 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