[Erledigt] CRTP Frage
-
Hallo Leute!
Ich habe ein Problem mit CRTP. Ich habe mir, um zu stehen was passiert eine kleines Beispiel gebaut:#include <iostream> template< typename T > class Worker { public: void show( ) { std::cout << typeid( T ).name( ) << std::endl; T* c = reinterpret_cast< T* >( this ); if( c->alter > 18 ) { std::cout << "okay, " << c->name.data( ) << " kann arbeiten!" << std::endl; }; } }; class Lehrling : public Worker< Lehrling > { private: public: std::string name; int alter; Lehrling( std::string n, int alt ) : name( n ), alter( alt ) { } }; int main( int arg, char* arvg[ ] ) { Lehrling l( "frank", 19 ); l.show( ); return 0; }Ich komme mit dieser Zeile nicht klar:
T* c = reinterpret_cast< T* >( this );Hier wird doch ein Pointer, vom Typ Lehrling, auf meine Klasse Worker erstellt. Wie kann den dieser Pointer auf die Werte wie Alter etc. zugreifen, wenn er auf eine ganz andere Klasse zeigt. Ich habe das Projekt mal in Olly versucht zu debuggen. Dort wird vor dem call
l.show( );die Adresse der Klasse Lehrling in eax geladen und und bei dem call verwendet um an den Namen und Alter zukommen. Deshalb: Warum muss der Cast so sein?
Ich würde mich sehr über eine ausführliche Erklärung freuen.MfG yihaaa
BTW: Ich benutze MSVS Express 2008, falls dies von Bedeutung sein könnte.
-
Klassen haben keine Adressen und du kannst auch keine Pointer auf Klassen haben. Du musst Klasse von Objekt trennen. Klasse ist die Plätzchenform, Objekt ist das Plätzchen.
Ein reinterpret_cast macht auch rein technisch gar nichts. Das kannst du nicht im Debugger nachvollziehen. Es sagt dem Compiler bloß, dass er die Schnauze halten soll, weil alles so stimmt, auch wenn er meint, es wäre falsch.
Außerdem ist hier der reinterpret_cast gar nicht notwendig, aufgrund der Verwandschaft zwischen Worker und Lehrling ist eigentlich ein static_cast angesagt. Der (hier) technisch auch nichts macht, sondern dem Compiler sagt, dass er den this-Zeiger als auf eine andere Klasse zeigend ansehen soll, im Gegensatz zu reinterpret_cast jedoch mit ein paar Prüfungen, ob das auch Sinn macht (du könntest hier z.B. keinen static_cast auf int* machen).
Zusammenfassend:
In Zeile 34 erzeugst du ein Lehrling-Objekt l. Auf dem l rufst du Worker<Lehrling>::show auf. Das geht, weil Lehrling von Worker<Lehrling> public erbt, also ein Worker<Lehrling> ist, wie man diese Verwandschaft beschreibt. Der this-Zeiger in Worker<Lehrling> zeigt also nicht bloß auf ein Worker<Lehrling>-Objekt, sondern auf ein Worker<Lehrling>-Objekt, welches gleichzeitig ein Teil eines Lehrling-Objektes (nämlich von l) ist. Daher ist es möglich, den this-Zeiger auch als einen Zeiger auf ein Lehrling-Objekt aufzufassen und das funktioniert, weil er tatsächlich auf ein Lehrling-Objekt zeigt.Beachte auch, wie ich oben immer Worker<Lehrling> als Name der Klasse genannt habe. Worker ist nämlich nur eine Vorlage (engl. template) für eine Klasse. Die Konstruktionszeichnung für eine Plätzchenform sozusagen. Erst Worker<Lehrling> ist eine konkrete Klasse und erst in l lebt (durch die Vererbung) eine konkrete Instanz (ein Plätzchen) von Worker<Lehrling>.
P.S.: Vielleicht ist die Analogie mit den Plätzchen nicht die beste. Wenn jemand ein bessere hat, bin ich interessiert, diese zu hören.
-
Okay, erst mal danke SeppJ!
So, damit ich das auch richtig verstanden habe:
Mit Lehrling l( name, 19 ); erstelle ich eine neue Objekt von Lehrling und von Worker< Lehrling >, die beide den gleichen this-Zeiger haben. Man muss ihn eben immer nur auf das richtig Objekt casten, damit der Compiler weiß, von welcher Klasse er z.B. Variablen und Funktionen nehmen soll?MfG yihaaa
-
yihaaa schrieb:
Man muss ihn eben immer nur auf das richtig Objekt casten, damit der Compiler weiß, von welcher Klasse er z.B. Variablen und Funktionen nehmen soll?
Nicht immer, normalerweise passt ja der Typ. Hier (also beim CRTP) jedoch kann der Compiler in Zeile 11 nicht wissen, dass die Methode später mal von einem Lehrling-Objekt aufgerufen wird.
-
Danke. Noch eine kleine Frage, wie könnte ich jetz verhindern, dass eine Objekt von Worker< X >, z.B. Worker< Lehrling > i; erstellt wird?
MfG yihaaa
-
Du kannst den Konstruktor des Worker-Templates protected machen, dann können nur davon abgeleitete Klassen ein Objekt erstellen:
template< typename T > class Worker { protected: Worker() {}; // ... }; int main() { Worker<Lehrling> ll; // Compilerfehler }
-
SeppJ schrieb:
Außerdem ist hier der reinterpret_cast gar nicht notwendig, aufgrund der Verwandschaft zwischen Worker und Lehrling ist eigentlich ein static_cast angesagt. Der (hier) technisch auch nichts macht, sondern dem Compiler sagt, dass er den this-Zeiger als auf eine andere Klasse zeigend ansehen soll, im Gegensatz zu reinterpret_cast jedoch mit ein paar Prüfungen, ob das auch Sinn macht (du könntest hier z.B. keinen static_cast auf int* machen).
Der
reinterpret_castist nicht nur nicht notwendig, er ist auch falsch.
Gibt ja Mehrfachvererbung.class Lehrling : public Irgendwas, public Worker< Lehrling > { };Und schon haben wir ein Problem.
Daher bei CRTP immer nur
static_cast!
-
@hustbaer: Könntest du vielleicht mal erklären warum das mit reinterpret_cast und den Mehrfachvererbungen falsch läuft? Ich kann mir das gerade nur schwer vorstellen.

MfG yihaaa
-
yihaaa schrieb:
@hustbaer: Könntest du vielleicht mal erklären warum das mit reinterpret_cast und den Mehrfachvererbungen falsch läuft? Ich kann mir das gerade nur schwer vorstellen.

MfG yihaaa
Das hat etwas mit der Anordnung im Speicher zu tun. Bei Vererbung besteht tatsächlich ein Mutterobjekt in dem beerbten Objekt. Angenommen Derived erbt von Base, dann sieht Derived so aus:
this -> ---------------- | Base-Teil | ---------------- | Derived-Teil | ----------------Das heißt, hier hat der Zeiger auf den Teil Derived::Base tatsächlich den gleichen Zahlenwert wie der Zeiger auf das ganze Objekt. Hier funktionieren sowohl static_cast als auch reinterpret_cast. Hat man aber Mehrfachvererbung, so können die verschiedenen Basisobjekte ja nicht an der gleichen Stelle liegen. Erbt Derived von Base1 und Base2 (in dieser Reihenfolge), dann sieht Derived so aus:
this -> ---------------- | Base1-Teil | ---------------- | Base2-Teil | ---------------- | Derived-Teil | ----------------Wenn man nun einen reinterpret_cast<Base2*> auf this macht, dann wird der Zahlenwert von this genommen und als Base2 interpretiert (nichts anderes macht reinterpret_cast). this zeigt hier aber auf ein Base1-Objekt und
man ist geficktdas ist nicht so günstig. Bei einem static_cast hatte ich oben geschrieben, dass er "hier" nichts machen würde und das war oben auch noch richtig. Bei der Mehrfachvererbung macht er aber tatsächlich was. Der Compiler kennt schließlich den Aufbau von Derived und weiß, wo der Base2-Teil eines Derived-Objektes relativ zum this-Zeiger des Derived-Objektes steht. Und wenn man nun sagt, dass man den Base2-Teil haben möchte, dann kann er dies korrekt umrechnen.(Eventuelles Padding zwischen den Objekten habe ich der Einfachheit wegen weggelassen. )
-
@yihaaa
SeppJ hat ja schon erklärt warum es in der Praxis ein Problem geben kann.Der
reinterpret_castist aber genaugenommen nicht deswegen falsch, weil es in der Praxis ein Problem geben kann, sondern weil der Standard einfach nicht garantiert dass es funktionieren muss.Bei
static_castdagegen ist es garantiert.
-
Wow, vielen dank euch beiden! Ich hatte mir das falsch vorgestellt, ich hatte nicht gewusst, dass wenn man eine Klasse mit Ableitungen erstellt, dass dann diese zu einer großen Klasse zusammen den selben this-Zeiger benutzen. Danke für die Erklärung!

MfG yihaaa