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 weilFont
undWordWrap
protected Member vonTControl
respektiveTButtonControl
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 einprotected
Property vonTControl
, das in abgeleiteten Klassen mit dem__published
Schlüsselwort wieder sichtbar gemacht wird. Sowas gibt`s in C++ nicht, und man kann auch nicht aufprotected
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 einprotected
Property vonTControl
, das in abgeleiteten Klassen mit dem__published
Schlüsselwort wieder sichtbar gemacht wird. Sowas gibt`s in C++ nicht, und man kann auch nicht aufprotected
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); }
-
Aber damit kommt man ja immer noch nicht an die
Font
-Eigenschaft.
Die KlasseTControl
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:- 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 einTControl
natürlich keinTControlElementAccessor
-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
anstattreinterpret_cast
. Und spätestens da sollte doch der Compiler meckern, wenn ich da was ungültiges reinfüttere.
-
Klar kann man in C++
protected
Zeugs einer Basisklassepublic
machen. Mitusing
:class Base { protected: int prot = 0; }; class Der : protected Base { public: using Base::prot; // <-- hier }; int main() { Der d; d.prot++; return d.prot; }
Man kann damit auch
public
Zeugs der Basisklasseprotected
oderprivate
machen. Wobei man das natürlich leicht umgehen kann, man muss ja nur auf ne Basisklassen-Referenz casten, und kann dann über die Basisklassen-Referenz trotzdem zugreifen. Kann aber praktisch sein wenn man zumindest Fehlverwendung durch Unachtsamkeit reduzieren will.p.S.: Das löst natürlich das originale Problem nicht, ist mir klar. Ist nur ne Antwort auf
@DocShoe sagte in Auf protected Member der Basisklasse zugreifen?:
. 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.