Kopier-Konstruktor
-
Als Anfänger stehe ich gerade auf dem Schlauch: Ich habe folgenden Code mit dem GNU-Compiler 4.6.3 übersetzt:
#include <iostream> class Test { private: int data; public: explicit Test(int i) : data(i) { std::cout << "Constuctor" << std::endl; } Test(const Test& t) { this->data = t.data; std::cout << "Copy-Constructor" << std::endl; } }; int main() { Test t1(1); Test t2(t1); Test t3(Test(2)); }Erwartet hätte ich:
`Constructor
Copy-Constructor
Constructor
Copy-Constructor
`
Ergebnis ist aber:
`Constructor
Copy-Constructor
Constructor
`
Wie wird denn t3 initialisiert? Nicht über den Copy-Constructor???
-
Mit
Test(2)erzeugst du ein temporäres Objekt, welches der Compiler in diesem Fall eliminieren darf (Stichwort: Copy Elision) und genau das passiert in deinem Beispiel offenbar auch...
-
Da greift die sogenannte copy elision. Das ist eine vom Sprachstandard explizit erlaubte Optimierung, bei der unnötige Kopien selbst dann wegoptimiert werden dürfen, wenn dadurch das beobachtbare Verhalten des Programms (hier Ausgabe im Konstruktor) verändert wird (das darf sonst bei keiner Optimierung passieren).
Guckst du zum Beispiel hier:
http://en.wikipedia.org/wiki/Copy_elision
-
Ihr habt tatsächlich recht: Wenn ich die Kompiler-Option
-fno-elide-constructorssetze, habe ich mein erwartetes Verhalten. Verstehe ich das richtig? Der Compiler sieht die Zeile:Test t3(Test(2));und denkt sich: Ich soll
t3erzeugen das identisch ist mitTest(2)? Dann rufe ich doch direktTest t3(2)auf.
-
Ulrike_1992 schrieb:
Verstehe ich das richtig? Der Compiler sieht die Zeile:
Test t3(Test(2));und denkt sich: Ich soll
t3erzeugen das identisch ist mitTest(2)? Dann rufe ich doch direktTest t3(2)auf.jap
-
Noch eine Frage habe ich: Angenommen, meine Klasse hat einen Kopierkonstruktor und einen Zuweisungsoperator: Bei einer Initialisierung mit einem Objekt in der Form
Test t3(Test(2));erwartet man nun den Aufruf des Kopierkonstruktors
Test::Test(const Test&)oder eine Eliminierung des temporären Objekts. Dürfte der Compiler aber stattdessen auch einen Zuweisungsoperatorconst Test& Test::operator=(const Test&)verwenden?
-
Nein, hier liegt ja keine Zuweisung vor. Ein Konstruktor erschafft aus dem Nichts (bzw. aus rohem Speicher) ein neues Objekt, ein Zuweisungsoperator verändert ein bestehendes Objekt.
-
So als Zusatzinfo, da es mich beim ersten Mal sehr überrascht hat und sicherlich auch einige andere überraschen wird: Selbst wenn keine richtige Kopie erstellt wird, darf sie wegoptimiert werden:
struct Foo { int blah; Foo(int i) : blah(i) { cout << "Konstruktor " << blah << '\n'; } Foo(const Foo& other): blah(1) {cout << "Falscher Kopierkonstruktor " << blah << '\n'; } }; int main() { Foo t1(5); Foo t2(t1); Foo t3(Foo(7)); cout << t3.blah << '\n'; }Hier darf (und wird) tatsächlich 7 anstatt 1 ausgegeben werden.
-
Könnte es auch nicht passieren, dass der Compiler zunächst ein leeres Objekt erzeugt und anschließend über den Zuweisungsoperator das Objekt initialisiert? Damit meine ich: Darf der Compiler einen Ausdruck wie
Test t3(Test(2));ersetzen durch etwas Ähnlichem wie:
Test t3(0); t3.operator=(Test(2));
-
Nein. Selbst das hier
Foo f = Foo(4); // oder Foo f2 = 4;Ist kein Zuweisungsoperator, sondern der Kopierkonstruktor.
-
Nein darf er nicht.
-
Aber in 2 Schritten doch nicht, oder?
Foo f = Foo(4); f = Foo(5);Erste Zeile: Initialisierung und damit Kopierkonstruktor oder Konstruktor mit Eliminierung des temporären Objekts. Zweite Zeile: Zuweisung. Die Frage wäre, ob der Compiler einen Initialisierungsausdruck umwandeln darf in eine Art Zweizeiler mit Zuweisung.
-
Nein darf er nicht.

-
Danke

-
Wie soll er denn zuerst ein "leeres" Objekt erstellen? Definier mal "leer" in C++-Sprech, dann siehst du, warum das nicht gehen kann (außer in Ausnahmefällen).
-
Mit "leerem Objekt" meine ich: Der Compiler macht nur Speicher für das Objekt frei ohne irgendwelche Komponenten zu initialisieren. Danach Zuweisung.
-
Ulrike_1992 schrieb:
Mit "leerem Objekt" meine ich: Der Compiler macht nur Speicher für das Objekt frei ohne irgendwelche Komponenten zu initialisieren. Danach Zuweisung.
Und genau das ist bei komplexen Datentypen nicht möglich. Denk mal nach, wie zum Beispiel der Zuweisungsoperator eines vectors aussehen muss. Der muss irgendwo den Speicher des überschriebenen vectors freigeben. Wenn der interne Datenzeiger aber niemals initialisiert wurde und stattdessen irgendeinen Müllwert hat, dann stürzt das zur Laufzeit ab.
-
Jain, das wäre ja genau der Zeitpunkt im operator new, wenn gearde Speicher allokiert wurde, aber der Konstruktor noch nicht aufgerufen wurde.
Aber letztlich hat SeppJ recht, hör auf ihn. Denn wenn du irgendwelche Spielereien im Speicher machen willst, kommst du schneller in Teufels Küche als der Teufel persönlich.
-
Die einzige sinnvolle Definition für "leeres" Objekt ist ein default-konstruiertes Objekt (also mit dem Konstruktor ohne Argumente). Es hat aber nicht jede Klasse einen Default-Konstruktor, so dass das nicht allgemein funktionieren kann.
-
Bashar schrieb:
Die einzige sinnvolle Definition für "leeres" Objekt ist ein default-konstruiertes Objekt (also mit dem Konstruktor ohne Argumente). Es hat aber nicht jede Klasse einen Default-Konstruktor, so dass das nicht allgemein funktionieren kann.
Meine Frage war weniger, ob es dem Compiler immer erlaubt ist, den Kopierkonstruktor durch eine Art Zuweisung zu ersetzen. Meine Frage ist mehr, ob der Compiler unter bestimmten Umständen, z.B. wenn ein Default-Konstruktor vorhanden ist, eine Initialisierung durch einen Kopier-Konstruktor dadurch ersetzen darf, dass er erst ein "default"-Objekt erzeugt, und anschließend mit einer Zuweisung dessen Komponenten initialisiert. Eigentlich sollten doch Kopier-Konstruktor und Zuweisungs-Operator eng miteinander verwandt sein. Ein Ausdruck wie
class Foo ... Foo f; Foo g(f);sollte doch das Gleiche bewirken wie
Foo f; Foo g; g = f;Folgende Klasse
Foowürde diese Erwartung erfüllen:struct Foo { int x; Foo() : x(0) {} Foo(const Foo& f) { this->operator=(f); } const Foo& operator=(const Foo& f) { this->x = f.x; return *this; } };Nun können anscheinend Kopier-Konstruktor und Zuweisungs-Operator unsinnigerweise so definiert sein, dass sie etwas vollkommen Anderes bewirken. Aber der Compiler könnte möglicherweise davon ausgehen, dass beide Komponenten dasselbe bewirken und ersetzt möglicherweise den Rumpf des Kopier-Konstruktors durch
this->operator=(irgendwas)Ich frage mich einfach: Wie weit darf der Compiler das erwartete Verhalten zugunsten einer Optimierung verändern.