C++Talk.NET Forum Index C++Talk.NET
C++ language newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

this / *this - grosser Unterschied in Konstruktor / Destru

 
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (German)
View previous topic :: View next topic  
Author Message
Thomas Mang
Guest





PostPosted: Sat Oct 15, 2005 9:32 pm    Post subject: this / *this - grosser Unterschied in Konstruktor / Destru Reply with quote




Ich habe beim Nachforschen, ob das was ich machen möchte OK ist, eine
bemerkenswerte Entdeckung gemacht:

Man nehme eine Klasse Foo, Bar, sowie eine Funktion Crash:

class Bar;

class Foo
{
public:
Foo(Bar &);
};

void Crash(Bar &);

class Bar
{
public:
Bar() : f(*this) {}
~Bar() {Crash(*this);}
private:
Foo f;
};


Ist so wie hier präsentiert natürlich nur aufs wichtigste beschränkt.

Hier worum es geht:
im Bar-Konstruktor wir das member f vom Typ Foo mit *this initialisiert. Das
Argument *this hat als zugehörigen Parameter ein Bar&. Wenn ich den Standard
richtig lese, ist das undefiniertes Verhalten.
Gemäß 8.5.3/1 muss eine Referenz mit einem Objekt initialisiert werden (die
anderen Möglichkeiten scheiden hier aus). Gemäß 3.8.1 beginnt ein Objekt
aber erst zu leben, wenn der Konstruktoraufruf _beendet_ ist. Im obigen
Schnippsel sind wir aber gerade dabei, den Konstruktor auszuführen. Also
wird die Referenz mit einem nicht-existierenden Objekt initialisiert.

Im Destruktor passiert dasselbe:
Gemäß 3.8.1 ist das Bar-Objekt schon tot, da der Destruktor begonnen hat,
und es wird ebenfalls eine Referenz mit einem toten Ding initialisiert.

Ich lese 8.5.3/1 so, dass das bereits UB ist, egal was dann die aufgerufenen
Funktionen machen (z.B. r-value Konvertierung).


Wenn man das ganze aber in pointer-Form schreibt, scheint alles OK zu sein:

class Bar;

class Foo
{
public:
Foo(Bar *);
};

void NoCrash(Bar *);

class Bar
{
public:
Bar() : f(this) {}
~Bar() {NoCrash(this);}
private:
Foo f;
};


Referenzen kommen hier nicht mehr vor, dafür der this-pointer.

In 12.7/2 steht, dass Formen des this-pointers legal ist, solange entweder
der Konstruktor _begonnen_ hat, oder der Destruktor _noch nicht beendet_
hat.. Beides ist hier der Fall.

Demnach macht es einen großen Unterschied, ob ich mein Bar-Objekt per
Referenz oder per Zeiger übergebe !

Gerade dass mit dem Destruktor macht mir sorgen, ich habe schon mehr als
einmal gesehen, dass sich Objekte woanders abmelden, und sich per Referenz
übergeben (u.a. habe ich soetwas selber schon mal gemacht.....).

Aber auch die Sache mit dem Zeiger dürfte nicht ganz harmlos sein. Hier ein
Bsp., was meine ganzen Recherchen erst gestartet hat, weil ich ein
schlechtes Bauchgefühl hatte, und anscheinend damit richtig lag:

class Bar;

class Holder
{
public:
Holder(Bar*);
};

class Bar
{
public:
Bar(Holder h) : h_(h) {}
private:
Holder h_;
};

class BarDerived : public Bar
{
public:
BarDerived() : Bar(Holder(this)) {}
};


Hier übergibt BarDerived den this-pointer an Holder. Und Rums. Denn der
BarDerived* wird in ein Bar* konvertiert, aber das Bar-subobject hat noch
nicht mit dem Konstruktor begonnen. Also findet eine ungültige
Zeigerkonvertierung statt. Kreire ich hingegen Bar per Standardkonstruktor,
und rufe dann im Konstruktorrumpf eine assignment-Funktion auf, die das
member h_ mit eime Holder belegt, ist alles in Ordnung - weill Bar dann
konstruiert wurde, und die Konvertierung BarDerived* -> Bar* in Ordnung ist.


Ist meine Interpretation der Lage richtig?
Also Code umschreiben.....


Thomas

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de
Back to top
Bob Hairgrove
Guest





PostPosted: Sun Oct 16, 2005 12:18 am    Post subject: Re: this / *this - grosser Unterschied in Konstruktor / De Reply with quote



On Sat, 15 Oct 2005 23:32:40 +0200, "Thomas Mang"
<a9804814 (AT) unet (DOT) univie.ac.at> wrote:

Quote:

Ich habe beim Nachforschen, ob das was ich machen möchte OK ist, eine
bemerkenswerte Entdeckung gemacht:

Man nehme eine Klasse Foo, Bar, sowie eine Funktion Crash:

class Bar;

class Foo
{
public:
Foo(Bar &);
};

void Crash(Bar &);

class Bar
{
public:
Bar() : f(*this) {}
~Bar() {Crash(*this);}
private:
Foo f;
};


Ist so wie hier präsentiert natürlich nur aufs wichtigste beschränkt.

Hier worum es geht:
im Bar-Konstruktor wir das member f vom Typ Foo mit *this initialisiert. Das
Argument *this hat als zugehörigen Parameter ein Bar&. Wenn ich den Standard
richtig lese, ist das undefiniertes Verhalten.

Das kommt ein wenig darauf an, wie man damit umgeht...das haben Sie
uns nämlich verschwiegen! Wenn Foo im Konstruktor lediglich eine
seiner Member-Variablen vom Typ Bar& damit initialisiert, kann man das
problemlos tun, wenn man sich an bestimmten Regeln hält.

Quote:
Gemäß 8.5.3/1 muss eine Referenz mit einem Objekt initialisiert werden (die
anderen Möglichkeiten scheiden hier aus). Gemäß 3.8.1 beginnt ein Objekt
aber erst zu leben, wenn der Konstruktoraufruf _beendet_ ist. Im obigen
Schnippsel sind wir aber gerade dabei, den Konstruktor auszuführen. Also
wird die Referenz mit einem nicht-existierenden Objekt initialisiert.

Jein ... zum Zeitpunkt der Initialisierung von Bar::f bedarf deren
Konstruktor lediglich eine Referenz auf Bar ... ob diese sich auf ein
gültiges Objekt bezieht oder nicht, entscheidet sich erst dann, wenn
man die Referenz wieder in ein Objekt umwandelt bzw. als Objekt
behandelt, z.B. mittels Aufruf einer Member-Funktion oder Verweis auf
eine Member-Variable (sprich: "Dereferenzierung"). Entscheidend ist,
dass dem Konstruktor von Foo() lediglich eine Referenz und kein Objekt
übergeben wird; somit wird der Destruktor von Bar nicht aufgerufen
werden, falls innerhalb des Konstruktorverlaufs von Foo eine Exception
auftritt.

Wenn Bar eine abgeleitete Klasse ist, gibt es ja ein gültiges Objekt:
nämlich deren Basisklasse, die ja bereits vor dem Erstellen von Bar::f
ausgeführt wurde. Wenn diese auch evtl. abstrakt ist, spielt das für
die Referenz selbst noch keine so grosse Rolle, solange man die
Referenz noch nicht im Konstruktor von Foo verwendet.

Quote:
Im Destruktor passiert dasselbe:
Gemäß 3.8.1 ist das Bar-Objekt schon tot, da der Destruktor begonnen hat,

Sie meinen wohl: "Destruktor von Foo"? Sie müssten noch bekannt geben,
ob Foo die Referenz lediglich in ein Member vom Typ Bar& kopiert, oder
sonst irgendwelche Sachen damit anstellt.

Im Allgemeinen gilt, dass alle Members von einem Objekt solange leben,
bis der Destruktor des Objekts fertig ist.

Quote:
und es wird ebenfalls eine Referenz mit einem toten Ding initialisiert.

Aber i.d.R. wird man im Destruktor keine Objekte und auch keine
Referenzen initialisieren...vor allem deshalb, weil ein Destruktor
keine Argumente hat. Sie haben uns hier einfach zu wenig Code gezeigt,
um daraus Schlüsse ziehen zu können, was Sie eigentlich im Schilde
führen...<g>

Quote:
Ich lese 8.5.3/1 so, dass das bereits UB ist, egal was dann die aufgerufenen
Funktionen machen (z.B. r-value Konvertierung).


Wenn man das ganze aber in pointer-Form schreibt, scheint alles OK zu sein:

class Bar;

class Foo
{
public:
Foo(Bar *);
};

void NoCrash(Bar *);

class Bar
{
public:
Bar() : f(this) {}
~Bar() {NoCrash(this);}
private:
Foo f;
};


Referenzen kommen hier nicht mehr vor, dafür der this-pointer.

In 12.7/2 steht, dass Formen des this-pointers legal ist, solange entweder
der Konstruktor _begonnen_ hat, oder der Destruktor _noch nicht beendet_
hat.. Beides ist hier der Fall.

Demnach macht es einen großen Unterschied, ob ich mein Bar-Objekt per
Referenz oder per Zeiger übergebe !

Der einzige Unterschied dürfte sein, ob der Zeiger == NULL ist oder
nicht ... Referenzen dürfen das nicht.

Quote:
Gerade dass mit dem Destruktor macht mir sorgen, ich habe schon mehr als
einmal gesehen, dass sich Objekte woanders abmelden, und sich per Referenz
übergeben (u.a. habe ich soetwas selber schon mal gemacht.....).

Aber auch die Sache mit dem Zeiger dürfte nicht ganz harmlos sein. Hier ein
Bsp., was meine ganzen Recherchen erst gestartet hat, weil ich ein
schlechtes Bauchgefühl hatte, und anscheinend damit richtig lag:

class Bar;

class Holder
{
public:
Holder(Bar*);
};

class Bar
{
public:
Bar(Holder h) : h_(h) {}
private:
Holder h_;
};

class BarDerived : public Bar
{
public:
BarDerived() : Bar(Holder(this)) {}
};


Hier übergibt BarDerived den this-pointer an Holder. Und Rums. Denn der
BarDerived* wird in ein Bar* konvertiert, aber das Bar-subobject hat noch
nicht mit dem Konstruktor begonnen. Also findet eine ungültige
Zeigerkonvertierung statt.

Die Zeigerkonvertierung ist gültig! Es gibt höchstens dann
undefiniertes Verhalten, wenn man versucht, den Zeiger zu
dereferenzieren, BEVOR das Objekt vollständig gebaut wurde. Das ist
nämlich das Schöne an Zeigern: sie dürfen ja auch gleich 0 sein, und
erst zu einem späteren Zeitpunkt mit der Adresse eines Objekts gefüllt
werden. Referenzen dürfen dagegen NIE gleich 0 sein, aber sehr wohl
dürfen sie -- solange man sie noch nicht verwendet! -- auf ein
unvollständiges Objekt zeigen.

Zugegebenermassen müsste ich erstmal die betreffenden Klauseln in der
Standard nachschlagen, allerdings ist das beschriebene Vorgehen betr.
Referenzen auch gängige Praxis und verursacht höchstens bei einigen
Compilern eine Warnung (aber keinen Fehler).

Quote:
Kreire ich hingegen Bar per Standardkonstruktor,
und rufe dann im Konstruktorrumpf eine assignment-Funktion auf, die das
member h_ mit eime Holder belegt, ist alles in Ordnung - weill Bar dann
konstruiert wurde, und die Konvertierung BarDerived* -> Bar* in Ordnung ist.


Ist meine Interpretation der Lage richtig?
Also Code umschreiben.....

IMHO -- falls irgendeine Chance besteht, das die übergebene Referenz
entweder vor Beendigung des Konstruktors verwendet wird, oder dass
deren Adresse == 0 sein könnte, dann müsste man Zeigern verwenden;
sonst ist auch eine Referenz i.O.

--
Bob Hairgrove
[email]NoSpamPlease (AT) Home (DOT) com[/email]

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
James Kanze
Guest





PostPosted: Sun Oct 16, 2005 1:55 pm    Post subject: Re: this / *this - grosser Unterschied in Konstruktor / De Reply with quote



Thomas Mang wrote:

Quote:
Ich habe beim Nachforschen, ob das was ich machen möchte OK
ist, eine bemerkenswerte Entdeckung gemacht:

Man nehme eine Klasse Foo, Bar, sowie eine Funktion Crash:

class Bar;

class Foo
{
public:
Foo(Bar &);
};

void Crash(Bar &);

class Bar
{
public:
Bar() : f(*this) {}
~Bar() {Crash(*this);}
private:
Foo f;
};

Ist so wie hier präsentiert natürlich nur aufs wichtigste beschränkt.

Hier worum es geht:

im Bar-Konstruktor wir das member f vom Typ Foo mit *this
initialisiert. Das Argument *this hat als zugehörigen
Parameter ein Bar&. Wenn ich den Standard richtig lese, ist
das undefiniertes Verhalten.

Warum? Was tust du damit in Foo::Foo? Der Pointer kannst du
schon kopieren.

Quote:
Gemäß 8.5.3/1 muss eine Referenz mit einem Objekt
initialisiert werden (die anderen Möglichkeiten scheiden hier
aus). Gemäß 3.8.1 beginnt ein Objekt aber erst zu leben, wenn
der Konstruktoraufruf _beendet_ ist. Im obigen Schnippsel sind
wir aber gerade dabei, den Konstruktor auszuführen. Also wird
die Referenz mit einem nicht-existierenden Objekt
initialisiert.

Stimmt. Wenn du genau §3.8 anschaust aber, merkst du, dass du
gewisse Dinge mit einem Zeiger machen kannst, sofort der
Speicher für das Objekt allokiert ist. Kopieren, zum Beispiel.
Versucht der Constructor von Foo etwas mit dem Bar-Objekt zu
tun, dann kannst du aber ganz schnell in Problemfälle raten.

Quote:
Im Destruktor passiert dasselbe:
Gemäß 3.8.1 ist das Bar-Objekt schon tot, da der Destruktor
begonnen hat, und es wird ebenfalls eine Referenz mit einem
toten Ding initialisiert.

Ich lese 8.5.3/1 so, dass das bereits UB ist, egal was dann
die aufgerufenen Funktionen machen (z.B. r-value
Konvertierung).

In der Praxis... Wenn du keine Vererbung hast, und du als
allerletzes in Constructor, oder als allererstes in Destructor,
etwas mit this machst, dann gibt es kein Problem.

Vorsicht aber: dass gilt nur insofern, dass du im Constructor
oder Destructor der meist abgeleiteten Klasse bist. Hat z.B. Bar
virtuelle Funktionen, und Crash ruft eine davon aus, dann wird
die Funktion von Bar aufgerufen, und nicht die Funktion von der
abgeleiteten Klasse.

Quote:
Wenn man das ganze aber in pointer-Form schreibt, scheint
alles OK zu sein:

class Bar;

class Foo
{
public:
Foo(Bar *);
};

void NoCrash(Bar *);

class Bar
{
public:
Bar() : f(this) {}
~Bar() {NoCrash(this);}
private:
Foo f;
};

Referenzen kommen hier nicht mehr vor, dafür der this-pointer.

In 12.7/2 steht, dass Formen des this-pointers legal ist,
solange entweder der Konstruktor _begonnen_ hat, oder der
Destruktor _noch nicht beendet_ hat.. Beides ist hier der
Fall.

Demnach macht es einen großen Unterschied, ob ich mein
Bar-Objekt per Referenz oder per Zeiger übergebe !

In der Praxis nicht.

Quote:
Gerade dass mit dem Destruktor macht mir sorgen, ich habe
schon mehr als einmal gesehen, dass sich Objekte woanders
abmelden, und sich per Referenz übergeben (u.a. habe ich
soetwas selber schon mal gemacht.....).

Da muss man vorsichtig mit polymorphischen Objekten angehen. Ich
habe persönlich nie ein Problem in Destructor erlebt, solange
die aufgerufene Funktion nicht an das Objekt angreift. Ich habe
aber wohl Probleme erlebt, wenn ich this als Parameter in der
Initialisierungsliste des Constructors benutzt habe.

--
James Kanze mailto: [email]james.kanze (AT) free (DOT) fr[/email]
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
Thomas Mang
Guest





PostPosted: Sun Oct 16, 2005 4:59 pm    Post subject: Re: this / *this - grosser Unterschied in Konstruktor / De Reply with quote



"Bob Hairgrove" <invalid (AT) bigfoot (DOT) com> schrieb im Newsbeitrag
news:0m33l1dqgel7m6uh23tkhib7foc1997m2c (AT) 4ax (DOT) com...
Quote:
On Sat, 15 Oct 2005 23:32:40 +0200, "Thomas Mang"
[email]a9804814 (AT) unet (DOT) univie.ac.at[/email]> wrote:


Ich habe beim Nachforschen, ob das was ich machen möchte OK ist, eine
bemerkenswerte Entdeckung gemacht:

Man nehme eine Klasse Foo, Bar, sowie eine Funktion Crash:

class Bar;

class Foo
{
public:
Foo(Bar &);
};

void Crash(Bar &);

class Bar
{
public:
Bar() : f(*this) {}
~Bar() {Crash(*this);}
private:
Foo f;
};


Ist so wie hier präsentiert natürlich nur aufs wichtigste beschränkt.

Hier worum es geht:
im Bar-Konstruktor wir das member f vom Typ Foo mit *this initialisiert.
Das
Argument *this hat als zugehörigen Parameter ein Bar&. Wenn ich den
Standard
richtig lese, ist das undefiniertes Verhalten.

Das kommt ein wenig darauf an, wie man damit umgeht...das haben Sie
uns nämlich verschwiegen! Wenn Foo im Konstruktor lediglich eine
seiner Member-Variablen vom Typ Bar& damit initialisiert, kann man das
problemlos tun, wenn man sich an bestimmten Regeln hält.


So sicher bin ich mir da nicht. Aber ich bin mir auch nicht sicher, dass ich
recht habe.

8.5.3/1:
"A variable declared to be a T&, that is "reference to type T" (8.3.2),
shall be initialized by an object, or..."
Also braucht man für die Initialisierung einer Referenz ein Objekt.

1.8/1:
"An object is a region of storage."
Speicher hätten wir zu diesem Zeitpunkt.

Muss das Objekt bei der Refernzinitialisierung also am Leben sein, oder
nicht? In w3.8/6 wird von einem "original object" geredet. Ein
Originalobjekt gibt es nicht, das wird ja gerade erst konstruiert. Dafür
wird in ersten Untersatz davon gesprochen, wenn nur Speicher da ist.

Alles in allem lese ich das nun so, dass es legal ist, wenn man die Regeln
in 3.8/6 einhält. Aber keineswegs sicher, und IMO auch nicht glücklich
formuliert. Wenn ein object eine Speicherregion ist, und man das in so
manchen Satz für object einsetzt, ergibt sich teilweise nicht viel Sinn. Mit
state of object / properties wäre es etwas anderes. Mein Eindruck ist, da
wird im Text nicht so genau unterschieden, was jetzt mit object _genau_
definiert ist. Und damit wird 8.5.3/1 in meinen Augen schwer zum _genauen_
interpretieren.



Quote:
Aber auch die Sache mit dem Zeiger dürfte nicht ganz harmlos sein. Hier
ein
Bsp., was meine ganzen Recherchen erst gestartet hat, weil ich ein
schlechtes Bauchgefühl hatte, und anscheinend damit richtig lag:

class Bar;

class Holder
{
public:
Holder(Bar*);
};

class Bar
{
public:
Bar(Holder h) : h_(h) {}
private:
Holder h_;
};

class BarDerived : public Bar
{
public:
BarDerived() : Bar(Holder(this)) {}
};


Hier übergibt BarDerived den this-pointer an Holder. Und Rums. Denn der
BarDerived* wird in ein Bar* konvertiert, aber das Bar-subobject hat noch
nicht mit dem Konstruktor begonnen. Also findet eine ungültige
Zeigerkonvertierung statt.

Die Zeigerkonvertierung ist gültig! Es gibt höchstens dann
undefiniertes Verhalten, wenn man versucht, den Zeiger zu
dereferenzieren, BEVOR das Objekt vollständig gebaut wurde.


Hier widersprechen sich IMO 12.7/2 und 3.8/5.
Laut 12.7/2 ist das ganze in Ordnung - der Konstruktor von BarDerived hat
begonnen, da es keine Basisklassen gibt, die "zwischen" Bar und BarDerived
liegen, ist alles erfüllt.
In 3.8/5 ist aber UB wenn der Zeiger auf das object, implizit in einen
Basisklassenzeiger umgewandelt wird. Allerdings in meinen Augen gleiche
Problematik wie oben, der Begriff object ist in meinen Augen hier nicht so
sauber verwendet, wie ich es mir für ein klares Verständnis wünschen würde.


Thomas

--
de.comp.lang.iso-c++ - Moderation: mailto:voyager+mod (AT) bud (DOT) prima.de
FAQ: http://www.voyager.prima.de/cpp/ mailto:voyager+send-faq (AT) bud (DOT) prima.de

Back to top
Display posts from previous:   
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (German) All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2006 phpBB Group
SEO toolkit © 2004-2006 webmedic.