Forum: Compiler & IDEs Unterschiedliche this pointer in Kindklasse


von Fragender (Gast)


Lesenswert?

Hallo zusammen,

ich verstehe nicht, warum beim folgenden Beispiel die werte von mOne und 
mOneInit nicht gleich sind.
1
class COne {
2
public:
3
   COne(int value)
4
   : mValue(value){
5
      mOne = this;
6
   }
7
   void init(){
8
      mOneInit = this;
9
   }
10
private:
11
   COne* mOne;
12
   COne* mOneInit;
13
   int mValue;
14
};
15
class CTwo : public COne {
16
public:
17
   CTwo(int value)
18
   :COne(value){
19
   }
20
};
1
COne container[2] = {COne(1), CTwo(2)};
2
container[0].init();
3
container[1].init();

in container steht danach folgendes:
[0] mOne 0x2001ffb4
[0] mOneInit 0x2001ffb4
[0] mValue 0x1
[1] mOne 0x2001ffcc
[1] mOneInit 0x2001ffc0
[1] mValue 0x2

container[0] ist ursprünglich ein COne und da ist mOne = mOneInit.
container[1] ist ursprünglich ein CTwo und da ist mOne != mOneInit - wie 
kommt das? Ich ging davon aus, dass die gleich sein müssen, weil die 
init()-funktion in COne steht.

von Karl H. (kbuchegg)


Lesenswert?

Fragender schrieb:

Das CTwo Objekt, welches du hier erzeugst (und welches sich an dieser 
Stelle seinen this Pointer merkt) ...

>
1
COne container[2] = {COne(1), CTwo(2)};
2
>

ist nicht dasselbe Objekt, wie das, welches dann im Array angelegt wird. 
Das Array kann nur COne Objekte speichern. Keine CTwo Objekte. D.h. von 
diesem CTwo Objekt wird, nachdem es erzeugt wurde, eine 'Kopie' gemacht, 
welches nur aus dem COne-Anteil von CTwo besteht. Das CTwo Objekt wird 
zu einem COne 'gestrippt' (so nennt man diesen Vorgang wenn ein Objekt 
aus einer abgeleiteten Klasse in Richtung Basisklasse zurechtgestutzt 
werden muss). Und da dazu ein neues Objekt erzeugt wird (erzeugt werden 
muss), hat dieses neue Objekt danach logischerweise auch einen anderen 
this Pointer.

Was da im Grunde steht bzw. passiert, kann man explizit so ausdrücken
1
COne container[2] = {COne(1), COne( CTwo(2) ) };

Wenn du dir mal die Copy Konstruktoren überschreiben würdest, dann 
müsste man eigentlich sehen, dass hier eine Kopie erzeugt wird.


Denn: die eigentlich interessante Frage ist eigentlich eine ganz andere. 
Sie lautet: warum wird eigentlich vom COne(1) Objekt keine Kopie während 
der Initialisierung gemacht? Denn eigentlich müsste das ja passieren! 
Schreibe ich
1
COne  a(3);
2
COne  Container[] = { a };

dann muss ja von a eine Kopie erzeugt werden, die als Objekt im 
Container weiterlebt. Es wird ja nicht das Objekt 'a' im Container 
gespeichert, sondern eine Kopie davon. a liefert ja nur die Vorlage, wie 
das Objekt im Container auszusehen hat.
Und prinzipiell ist es auch nicht das Objekt
1
COne  Container[] = { COne(4) };
das du hier in der Initalisierung angegeben hast, welches gespeichert 
wird, sondern (völlig analog) eine Kopie davon. Der Fall unterscheidet 
sich ja erst mal in nichts von dem Fall, in dem das Objekt a zur 
Initialisierung benutzt wird. Dort war es ein Objekt mit einem Namen, 
hier ist es ein temporäres Objekt. Aber der Vorgang ist der gleiche.
Hier in diesem Fall greift allerdings eine Optimierung, die in C++ 
zulässig ist. Sie besagt, dass in manchen Fällen der Compiler das 
Erzeugen von Kopien weglassen kann, wenn er das Objekt quasi gleich am 
Ziel erzeugen kann. Und genau das ist hier der Fall. Der aufwendige, 
eigentlich richtige Prozess
  erzeuge ein temporäres Objekt mit dem vorgesehenen Konstruktor
  erzeuge das Objekt Container[0], wobei dieses Objekt mittels
      Copy Constructor aus dem temporären Objekt erzeugt wird
  zerstöre das temporäre Objekt
kann und darf vom Compiler zu
  erzeuge das Objekt Container[0] mit dem vorgesehenen Konstruktor
abgekürzt werden. Das darf er auch dann tun, wenn der Copy-Konstruktor 
Nebenwirkungen hat, die durch die Optimierung dann wegfallen.


D.h. die Frage lautet bei dir eigentlich nicht
  * warum sind beim 2.ten Objekt die Pointer unterschiedlich?
Sondern die korrekte Fragestellung würde lauten
  * warum sind sie es beim ersten Objekt nicht?

Und die Antwort lautet
weil der Compiler beim ersten Objekt das Erzeugen der Kopie mithilfe der 
'Named Return Value Optimization' verhindern kann, während das im 2.ten 
Fall aufgrund des Strippings nicht geht.
Das wäre die ausführliche technische Antwort, was da eigentlich abgeht. 
Musst du dir so nicht merken, die Kurzform von oben ("wegen des 
Strippings") reicht.

von Fragender (Gast)


Lesenswert?

Vielen Dank für die ausführliche Antwort!
So gesehen macht das Verhalten natürlich schon Sinn...

Jetzt stellt sich mir trotzdem die Frage, wie ich das Umkopieren 
vermeiden kann.

Spontan ist mir noch folgende Lösung eingefallen:
1
COne* container[2] = {new COne(1), new CTwo(2)};

Sie gefällt mir allerdings nicht sooo gut, da new verwendet wird.

Grundsätzlich bräuchte ich einfach ein Array mit Instanzen (oder auch 
"nur" Referenzen) auf die erwähnten Klassen. Etwas umständlicher könnte 
ich es auch so realisieren:
1
COne one(1);
2
CTwo two(2);
3
COne* container[2] = {&one, &two};

Gibt es auch eine Möglichkeit das Array zu erstellen ohne erst das 
Objekt zu erzeugen und dann ins Array zu kopieren?

von Karl H. (kbuchegg)


Lesenswert?

Fragender schrieb:
> Vielen Dank für die ausführliche Antwort!
> So gesehen macht das Verhalten natürlich schon Sinn...
>
> Jetzt stellt sich mir trotzdem die Frage, wie ich das Umkopieren
> vermeiden kann.

Gar nicht.
Solange du ein Array aus COne Objekten hast, in welches du CTwo Objekte 
ablegen willst (warum eigentlich?) hast du IMMER das Problem, dass mit 
den CTwo Objekten etwas geschehen muss, damit sie ins Array können (und 
damit dann zu COne Objekten werden).

Wenn dir das nicht gefällt, dann musst du ein Array von Pointern 
aufbauen. Dann kannst du selbstverständlich Pointer auf alle von COne 
abgeleiteten Objekte darin speichern. Was sowieso sinnvoller ist, wenn 
du eine Klassenhierarchie hast.


> Gibt es auch eine Möglichkeit das Array zu erstellen ohne erst das
> Objekt zu erzeugen und dann ins Array zu kopieren?

Der Knackpunkt ist nicht das kopieren oder dergleichen.
Der Knackpunkt besteht darin, was du eigentlich im Array haben willst! 
Wenn ein CTwo Objekt seine 'CTwo-Eigenschaft' behalten soll, dann geht 
das nur über Pointer bzw. Referenzen. Nur dann kannst du polymorphes 
Verhalten erzielen.

Aber solange du ein Array aus COne Objekten baust, kann man da drinn 
auch nur COne Objekte speichern und nichts anderes. Und wenn das 
Stripping bedeutet, dann bedeutet das eben Stripping. Ein CTwo Objekt 
ist eben als Ganzes gesehen kein COne Objekt. In ihm steckt eines 
drinnen, aber als komplettes Objekt betrachtet ist es ein CTwo Objekt 
und kein COne.

von Karl H. (kbuchegg)


Lesenswert?

Fragender schrieb:

>
1
> COne one(1);
2
> CTwo two(2);
3
> COne* container[2] = {&one, &two};
4
>
>
> Gibt es auch eine Möglichkeit das Array zu erstellen ohne erst das
> Objekt zu erzeugen und dann ins Array zu kopieren?

Nein.
Denn mit der direkten Angabe

COne* container[2] = { &COne(1), &CTwo(2) };

hast du zuallererst mal unbenannte temporäre Objekte erzeugt. Und diese 
Objekte werden klarerweise (wie alle temporären Objekte) wieder 
zerstört. D.h. du hast dann ein Array mit Pointern auf Objekte, die 
nicht mehr existieren.

D.h. du brauchst Objekte, deren Lebensdauer nicht durch das Erzeugen des 
Arrays begrenzt ist. Und genau das sind temporäre Objekte eben nicht, 
sondern dazu musst du das Objekt schon mit anderen Mitteln erzeugen.

> Sie gefällt mir allerdings nicht sooo gut, da new verwendet wird.
Na, na.
So schlimm ist das auch wieder nicht :-)
Es gibt eben Dinge, die auch in C++ nur mittels new/delete machbar sind.
Aber: das alles kann man sich ja auch in einer eigenen Container-Klasse 
verstecken :-) die die dynamisch allokierten Objekte managed.


Wozu brauchst du das eigentlich konkret?
Normalerweise muss ein Objekt seinen this-Pointer ja überhaupt nicht 
kennen (in der Form, dass man den abspeichern müsste). Das einzige, was 
interessiert ist, welchen dynamischen Datentyp hat das Objekt.
Mir scheint, du bist gerade am Weg, Polymorphie und virtuelle Funktionen 
zu entdecken.

von Fragender (Gast)


Lesenswert?

Im konkreten Fall werden die this-Pointer selbstverständlich nicht nur 
im eigenen Objekt gespeichert :)

COne ist im konkreten Fall der "default" Schrittmotor. Der this-Pointer 
wird darin verwendet, um sich zB beim InterruptManager zu registrieren.
CTwo ist ein "spezieller" Schrittmotor mit erweiterter/anderer 
Funktionalität.

Sämtliche Schrittmotoren werden unter anderem von einem - nennen wir ihn 
mal Controller - Controller angesteuert. Der Controller braucht nicht zu 
wissen, welcher ein CTwo oder ein COne ist und kennt CTwo überhaupt gar 
nicht. Übergeben werden die Schrittmotoren in einem Array.

Habe es nun nach Variante 2 (Array mit Pointern - ohne new) 
implementiert.

Danke nochmals für die Hilfe.

von Karl H. (kbuchegg)


Lesenswert?

Fragender schrieb:
> Im konkreten Fall werden die this-Pointer selbstverständlich nicht nur
> im eigenen Objekt gespeichert :)
>
> COne ist im konkreten Fall der "default" Schrittmotor. Der this-Pointer
> wird darin verwendet, um sich zB beim InterruptManager zu registrieren.
> CTwo ist ein "spezieller" Schrittmotor mit erweiterter/anderer
> Funktionalität.
>
> Sämtliche Schrittmotoren werden unter anderem von einem - nennen wir ihn
> mal Controller - Controller angesteuert. Der Controller braucht nicht zu
> wissen, welcher ein CTwo oder ein COne ist und kennt CTwo überhaupt gar
> nicht. Übergeben werden die Schrittmotoren in einem Array.

Jau.
Du bist auf dem Weg zu Polymorphie.

Und Polymorphie erfordert Pointer bzw. Referenzen, damit der dynamische 
Typ des Objekts erhalten bleibt, selbst wenn er sich vom statischen Typ 
unterscheidet.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.