Auf protected Member der Basisklasse zugreifen?



  • Hi,

    in der VCL wird in Delphi bspw. folgender Code verwendet:
    (Control ist dabei eine TWinControl Eigenschaft)

    Canvas.Font := TWinControlClass(Control).Font;
    
    if Control is TCustomCheckBox then
          FWordWrap :=  TCustomCheckBox(Control).WordWrap;
    

    Wenn ich das gleiche in C++ versuche bspw. über dynamic_cast geht das nicht weil Font und WordWrap protected Member von TControl respektive TButtonControl sind. Ich hab dann noch festgestellt, dass das in Delphi auch nur geht, weil die alle in derselben Unit sind und innerhalb einer Unit die Sichtbarkeiten dort nicht gelten.

    Meine Frage ist jetzt:
    Gibt es eine Möglichkeit das irgendwie in C++ umzusetzen? Also für alle Elemente einer bestimmten Basisklasse auf die o.g. protected Eigenschaften zuzugreifen? Oder muss ich auf alle möglichen abgeleiteten Klassen prüfen, in der die Eigenschaften dann published sind? Nur die kann ich ja gar nicht alle kennen, vor allem wenn man später noch ne weitere Klasse von TCustomCheckBox etc. ableitet. Ich hab aktuell allein 3 Klassen, die von TCustomCheckBox abgeleitet sind und bei TWinControl sind das ja dutzende...

    (Ist das nicht auch der Sinn von Basisklassen, dass man ne gemeinsame Schnittstelle hat? Warum sind dann in der VCL die ganzen Eigenschaften protected?)



  • @drummi Ich glaube, dass Stichwort, was du suchst ist Public inheritance.
    Also sowas:

    class derived : public base
    {
    //Code Kram 
    }


  • Schwierig, weil das Sichtbarkeit in Delphi anders gehandhabt wird als mit C++. Font ist ein protected Property von TControl, das in abgeleiteten Klassen mit dem __published Schlüsselwort wieder sichtbar gemacht wird. Sowas gibt`s in C++ nicht, und man kann auch nicht auf protected Elemente zugreifen.



  • @DocShoe sagte in Auf protected Member der Basisklasse zugreifen?:

    Schwierig, weil das Sichtbarkeit in Delphi anders gehandhabt wird als mit C++. Font ist ein protected Property von TControl, das in abgeleiteten Klassen mit dem __published Schlüsselwort wieder sichtbar gemacht wird. Sowas gibt`s in C++ nicht, und man kann auch nicht auf protected Elemente zugreifen.

    Das geht in der Tat nicht in C++, aber man könnte vielleicht für Daten-Member einen ähnlichen Effekt wie mit __published erzielen, ohne die Basisklasse ändern zu müssen (sofern ich den richtig verstanden habe):

    #include <iostream>
    
    class A
    {
        protected:
            int property = 42;
    };
    
    class B : A
    {
        public:
            int& property = A::property;
    };
    
    int main()
    {
        B b;
        std::cout << b.property << std::endl; 
    }
    

    https://godbolt.org/z/TxjMW7WY1

    Nur so ne spontane Idee.



  • Hab mich grad mit nem Arbeitskollegen unterhalten, der in Delphi wesentlicher fitter ist als ich. Sein Vorschlag ist sich so eine Konstruktion zu bauen:

    class TControlElementAccessor : public TControl
    {
    __published:
       // ändert die Sichtbarkeit von protected auf __published und ist damit zugreifbar
       __property Font;
    };
    

    Im Anwendungsfall sieht das dann so aus:

    void f( TControl* control )
    {
       if( control )
       {
          TControlElementAccessor* accessor = reinterpret_cast<TControlElementAccessor*>( control );
          accessor->Font->...
       }
    }
    

    Da sollte ein C++ Profi mal draufgucken, ob dieser cast kein UB bedeutet, ich bin mir da nicht sicher.



  • @DocShoe sagte in Auf protected Member der Basisklasse zugreifen?:

    Da sollte ein C++ Profi mal draufgucken, ob dieser cast kein UB bedeutet, ich bin mir da nicht sicher.

    Das funktioniert nur wenn der Übergabeparameter auch wirklich vom Typ TControlElementAccessor ist. Die Funktion ist also fehleranfällig.

    Eine etwas schickere Lösung wäre:

    class TControl
    {
    
    };
    
    
    class TControlElementAccessor : public TControl
    {
    public:
      int Font;
    };
    
    
    template<typename T>
    void f(T& control)
    {
      if constexpr (std::is_same_v<T, TControlElementAccessor>)
      {
        control.Font = 1;
      }
      // Evt. hier ein static assert einbauen.
    }
    
    
    int main()
    {
      TControlElementAccessor T1;
      TControl T2;
    
      f(T1);
      f(T2);
    }
    
    


  • @Quiche-Lorraine

    Aber damit kommt man ja immer noch nicht an die Font-Eigenschaft.
    Die Klasse TControl ist in Auszügen so definiert:

    class TControl
    {
       TFont* FFont;
    protected:
       __property Font = { read=GetFont, write=SetFont };
    
       TFont* __fastcall GetFont()
       {
          return FFont;
       }
    
       void __fastcall SetFont( TFont* font )
       {
          // internes Zeugs
          FFont = font;
          // noch mehr internes Zeugs 
       }
    };
    

    Die obige Idee setzt darauf auf, dass alle TControl Eigenschaften in des Basisklasse vorhanden sind und über die Zugriffsklasse auch nur auf die Eigenschaften der Basisklasse zugegriffen wird. Das C++ Pendant sieht wohl so aus:

    struct Base
    {
       int X = 0;
    
       virtual ~Base() = default;
    };
       
    class Derived1 : public Base
    {
       // Zeugs
    };
    
    class Derived2 : public Base
    {
       // nix
    };
    
    // ist dieses Vorgehen wohldefiniert?
    void f( Derived1* dev1 )
    {
       // upcast gelingt immer
       Base* base = dev1;
    
       // downcast mit der Intention, nur auf Elemente der Basisklasse zuzugreifen. UB?
       Derived2* dev2 = reinterpret_cast<Derived2*>( base );
       int x = dev2->X;
    }
    


  • Danke euch ALLEN schonmal für die verschiedenen Ideen!
    Die von @DocShoe vorgeschlagene Variante funktioniert soweit gut, nur bin ich mir auch nicht ganz sicher, ob das undefiniertes Verhalten ergibt. Ich hab ein bisschen recherchiert und folgendes gefunden:

    1. Any object pointer type T1* can be converted to another object pointer type cv T2*. This is exactly equivalent to static_cast<cv T2*>(static_cast<cv void*>(expression)) (which implies that if T2's alignment requirement is not stricter than T1's, the value of the pointer does not change and conversion of the resulting pointer back to its original type yields the original value). In any case, the resulting pointer may only be dereferenced safely if the dereferenced value is type-accessible.

    Dazu gab' noch folgendes Beispiel:

    struct S { int x; };
    struct T { int x; int f(); };
    struct S1 : S {};    // standard-layout
     
    S s = {};
    auto p = reinterpret_cast<T*>(&s); // value of p is "pointer to s"
    auto i = p->x; // class member access expression is undefined behavior;
                   // s is not a T object
    p->x = 1; // undefined behavior
    p->f();   // undefined behavior
     
    S1 s1 = {};
    auto p1 = reinterpret_cast<S*>(&s1); // value of p1 is "pointer to the S subobject of s1"
    auto i = p1->x; // OK
    p1->x = 1;      // OK
    

    Quelle: https://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_accessibility

    Ist das jetzt nun UB oder nicht? Ich meine, wir leiten mit dem

    class TControlElementAccessor : public TControl { __published: __property Font; };
    TControlElementAccessor* accessor = reinterpret_cast<TControlElementAccessor*>( control );
    TFont* font = accessor->Font;
    

    ja nur von der Base-Klasse ab und verändern da nix außer die Sichtbarkeit. Das wäre doch eigtl so wie beim obigen Beispiel mit S1. Nur auf der anderen Seite ist ein TControl natürlich kein TControlElementAccessor-Objekt, sondern nur andersrum, was widerum für UB sprechen würde. 🤯

    Edit: Eigtl müsste das ganze legal sein, denn es funktioniert auch mit static_cast anstatt reinterpret_cast. Und spätestens da sollte doch der Compiler meckern, wenn ich da was ungültiges reinfüttere.


Anmelden zum Antworten