Copy-Constructor und operator*
-
Hi!
Ich habe eine Frage bezüglich Copy-Konstruktor und dem Operator*. Ich mache gerade ein Softwarepraktikum an der Uni und soll dort Datentypen entwerfen. Ich habe aber leider ein Problem mit dem CoyConstructor. Wenn ich ein Objekt folgendermaßen initialisiere:
matrix c = a*b;Warum wird hier kein Copy-Constructor aufgerufen? Hier mal ein Beispielcode:
#include <iostream> using namespace std; class dummy { public: dummy() { cout << "Default-Constructor" << endl; }; dummy( const dummy& other ) { cout << "Copy-Constructor" << endl; //mache deep copy }; dummy operator*( const dummy & other ) { //code cout << "Operator*" << endl; dummy tmp; //lokales Obj für Matrixmultiplikation // mache multiplikation return tmp; }; dummy& operator=(const dummy& other) { cout << "Operator=" << endl; //erstelle deep-copy etc. return *this; }; }; int main() { dummy a; dummy b; dummy c = a * b; return 0; }Die Ausgabe ist num folgende:
Default-Constructor Default-Constructor Operator* Default-ConstructorErwarten würde ich aber eher
Default-Constructor Default-Constructor Operator* Default-Constructor [b]Copy-Constructor[/b]Kann mir jemand erklären, warum der Copy-Construktor nicht verwendet wird, um c zu initialisieren, bzw. wie wird c sonst initialisiert?
Vielen Dank!
miho
-
Ich würde mal auf Optimierungen des Compilers tippen (was bedeutet, daß 'tmp' gar nicht angelegt, sondern das "Produkt" direkt in c reinkonstruiert wird).
-
Danke für deine Antwort. Ich habe Optimierung komplett ausgeschaltet, da ich auch den Verdacht hatte. Aber es hat nichts gebracht
Kann man vielleicht den CopyConstruktor irgendwie erzwingen?Gruß,
miho
-
Hi,
ich weiß zwar auch noch nicht, warum, aber wenn der operator*() eine const& zurückliefert, funktioniert's wie erwartet... (gcc)
Hat vielleicht irgendwas mit "temporären Objekten" und/oder RVO zu tun ...Gruß,
Simon2.
-
meine vermutung: du machst folgendes
dummy a; dummy b; dummy c = dummy();was auch
dummy a; dummy b; dummy c();ist, zwischendurch wird der *operator aufgerufen, aber der ist ohne jegliches belangen fuer den rest. fuehre mal ne neutzliche operation im operator durch.
-
rapso schrieb:
meine vermutung: du machst folgendes
dummy a; dummy b; dummy c = dummy();was auch
dummy a; dummy b; dummy c();ist, zwischendurch wird der *operator aufgerufen, aber der ist ohne jegliches belangen fuer den rest. fuehre mal ne neutzliche operation im operator durch.
Aber warum wird der CopyCtor genutzt, wenn operator*() eine "dummy const&" zurückgibt ?
*in-den-wald-ruf*: CAAAAAAAMPER !!! Wir brauchen Dich hier ?

Gruß,
Simon2.
-
Weil er nicht gerufen wird?
int main() { dummy a; dummy b; dummy c( a * b); return 0; }MfG Kimmi
-
eine classe mit einem konstruktor z.b. myclass(int)
kann entweder mit
myclass a(5)
oder
myclass a = 5 aufgerufen,darum wird der nicht aufgerufen
-
kimmi schrieb:
Weil er nicht gerufen wird?
int main() { dummy a; dummy b; dummy c( a * b); return 0; }MfG Kimmi
Und wo ist der Unterschied zu
int main() { dummy a; dummy b; dummy tmp; tmp = ( a * b); dummy c = tmp; return 0; }... wo dann der CopyCtor auferufen wird ? Macht es soviel aus, wenn operator*() ein temporäres Objekt erzeugt ?
Gruß,
Simon2.
-
Simon2 schrieb:
Aber warum wird der CopyCtor genutzt, wenn operator*() eine "dummy const&" zurückgibt ?
Denk mal über
typedef const dummy const_dummy; int main() { dummy x = dummy(); dummy y = const_dummy(); }nach hinsichtlich der Voraussetzungen für die Auslassung des CopyOps.
-
camper schrieb:
...der Voraussetzungen für die Auslassung des CopyOps.
Leider kenne ich nicht "die Voraussetzungen für die Auslassung des CopyOps"...
... und warum der CopyCtor ausgelassen wird, weiß ich auch nicht.
Gruß,
Simon2.
-
Für mich riecht das ganz klar nach RVO. Eigentlich sollte ein Compiler bei dem Code mit ausgeschalteten Optimierungen einen Aufruf des copy-ctor erzeugen, aber egal.
-
hustbaer schrieb:
...aber egal.
Finde ich jetzt nicht .... immerhin schien camper eine Lösung zu haben (die er leider noch nicht aufgedeckt hat).
Außerdem sollte RVO doch Seiteneffekte erkennen/berücksichtigen .. das tut sie aber im vorliegenden Fall nicht (habe mal spaßeshalber einen CopyCtor mit Seiteneffekt gebastelt und der wird auch nicht aufgerufen).
Und schließlich ist es IMO kein sooo exotischer Fall, dass man sowieso nicht in diese Falle tappen könnte.Wäre schon schön, wenn hier klar würde, wo der (Denk-)fehler liegt.
Gruß,
Simon2.
-
Ich nehme an RVO ist eine selbstverständliche Optimierung und keine, die man je nach Bedarf einschalten können soll (wäre imo auch logisch gesehen gut).
Was das Beispiel angeht, hat Simon2 ja schon gemutmaßt, dass es sich um RVO handelt. Hier wird das Objekt tmp (welches im operator* erzeugt und zurückgegeben wird) direkt in c hineinkonstruiert, es wird also kein Copy-Ctor benötigt.
Dummy c = a*b; // äquivalent zu Dummy c( a*b ); // äquivalent zu Dummy c( a.operator*( b ) ); // Pseudocode für operator* Dummy operator*( Dummy const& left, Dummy const& right ) returns: Dummy tmp; { } // Ablauf nach RVO: Dummy c; operator*( c, a, b ); void operator*( Dummy& target, Dummy const& left, Dummy const& right ) { }Dummy "c" ist also Dummy "tmp", weshalb nirgendwo etwas kopiert werden muss.
Bei der Rückgabe einer (konstanten) Referenz hingegen sollte klar sein, dass die Möglichkeit nicht besteht, die Initialisierung des Objekts in den Operator zu verlegen, da eine Rückgabe einer Referenz auf ein lokales Objekt undefiniert wäre. Demnach muss das zurückgegebene Objekt ausserhalb der Funktion existieren (this-Objekt, global, jedoch nicht im Aufrufer, es seidenn der Operator gibt einen der Operanden zurück ...), wodurch dessen Konstruktion nicht von der Funktion in den Aufrufer verlegt werden kann, wodurch eine Kopie erforderlich wird.
Dummy c( a*b ); // Kopie erforderlich Dummy const& operator*( Dummy const& left, Dummy const& right ) { return somethingNotLocal; }PS: Dieser Beitrag dient auch für mich selbst dazu, die Zusammenhänge auseinanderzuklamüsern. Sollte hier erwiesenermassen Quatsch stehen würde ich Euch (camper) bitten, das möglichst klarzustellen

-
Hi,
mir scheint's auch immer mehr RVO zu sein...
allerdings bin ich ein wenig beunruhigt, weil es mein Verständnis von der Konstruierei doch ein wenig durcheinanderwirbelt.
Letztlich bleibt:
dummy f() { ... }; int main() { dummy a = f(); ...In dem Fall bestimmt nicht "der Erzeuger von a", welcher Konstruktor aufgerufen wird (der evtl. erwartete CopyCtor), sondern der Implementierer von f().
Das beschränkt ein wenig die Möglichkeiten des dummy-Entwicklers, weil hier RVO wohl letztlich von einer "Standard-Copy-Konstruktion" (also Objekte sind hinterher im Speicher identisch) ausgeht und irgendwelche exotischen Seiteneffekte (fällt mir gerade kein Beispiel ein; Referenzcounting würde sich daran ja nicht stören) nicht einbauen kann.
Naja, immerhin kann er das Kopieren noch ganz verbieten ...Hmmm strange....
Gruß,
Simon2.
-
Sieh es doch mal so.Die Compiler sind jetzt so intelligent, dass sie Dinge optimieren an die du nie denken würdest, egal ob du das so willst oder nicht.

Ich hatte den Test auch gemacht und mich schon gewundert, das der g++ im debugmodus den CopyCTor rausoptimiert. Sobald im Operator* etwas vernünftiges stand war er aber wieder drin.
-
Hi,
was war denn "etwas Vernünftiges" ?
Gruß,
Simon2.
-
dummy operator*( const dummy & other ) { //code cout << "Operator*" << endl; dummy tmp(other); //lokales Obj für Matrixmultiplikation // mache multiplikation return tmp; };
-
Braunstein schrieb:
dummy operator*( const dummy & other ) { //code cout << "Operator*" << endl; dummy tmp(other); //lokales Obj für Matrixmultiplikation // mache multiplikation return tmp; };Naja, den CopyCtor explizit aufrufen, kann ja jeder....

... und RVO findet ja trotzdem statt.Mein "Vernünftiges" sah nämlich so aus:
class dummy { int i; // ... }; dummy operator*( const dummy & other ) { //code cout << "Operator*" << endl; dummy tmp; tmp.i = i * other.i; return tmp; };.... und da wird KEIN CopyCtor aufgerufen, obwohl es mir als sehr vernünftige Implementation dieses operators erschien...

Gruß,
Simon2.
-
Das sehe ich auch so. ....... jetzt!

Obwohl die Verwendung des CopyCtor hier den weiteren Aufruf eines Default-Konstrukltors bedingen würde (für das temporäre Objekt). Sonst gäbe es ja nichts zum kopieren. Und genau das scheint der Compiler ja wegzuoptimieren.
Ich gebe zu, der direkte Aufruf des CopyCTor war kein so gutes Beispiel.
-
Simon2 schrieb:
Außerdem sollte RVO doch Seiteneffekte erkennen/berücksichtigen ..
Wäre mir neu. Wie kommst du darauf?
RVO ist nur eine der ganzen Stellen wo der Programmierer nicht genau wissen kann wo bzw. wie oft seine Objekte rumkopiert werden...Ein Copy-Ctor muss - damit eine Klasse in C++ sinnvoll einsetzbar ist - immer so geschrieben sein dass
Foo a = x;zu
Foo tmp = x; Foo a = tmp;äquivalent ist -- also was x und a angeht.