C++ Konstruktor-Aufruf-Suche im Code

D

derBobby

Gast
Hallo,

ich habe hier eine überladene Operatorfunktion und die scheint irgendwo einen Konstruktor zu verwenden, ich verstehe nur nicht wo oder warum. Kann mir das jemand erklären?

Meine main führt folgendes aus:

Code:
Bruch b1(1,2);
Bruch b2(2,3);
Bruch b3 = b1 * b2;

Die überladene Operatorfunktion:
Code:
const Bruch & Bruch::operator *= (const Bruch &b)
{
[INDENT]return *this = *this * b;[/INDENT]
}

Der Konstruktor:
Code:
Bruch::Bruch(int z, int n) : zaehler(z), nenner(n)
{
[INDENT]cout << "Konstruktor" << endl; [/INDENT]
}

Der Kopier-Konstruktor:
Code:
Bruch::Bruch(const Bruch &rhs)
{
[INDENT]cout << "Kopier-Konstruktor" << endl;
zaehler = rhs.zaehler; nenner = rhs.nenner; [/INDENT]
}

Meiner erwartete Ausgabe wäre:
Konstruktor...
Konstruktor...
Konstruktor...

Die Ausgabe ist diese:
Konstruktor...
Konstruktor...
Konstruktor...
Konstruktor...

Also ein Konstruktoraufruf mehr als gedacht. Wo liegt mein Denkfehler? Oder wo meine Unwissenheit?
 
Zuletzt bearbeitet von einem Moderator:
1. Bruch b1(1,2);
2. Bruch b2(2,3);
3. + 4. Bruch b3 = b1 * b2;

Der dritte ist Bruch b3
der vierte ist das Ergebnis b1 * b2 und der wird dann b3 zugewiesen.
Baue ggf. einen Kopierkonstruktor Bruch::Bruch(Bruch b) {...}
und mache
Bruch b3 (b1 * b2);
dann hast Du nur 3 Aufrufe zwei Deines Konstruktors und einen des Kopierkonstruktors.
 
Fehlt da nicht noch der Code für die reine Multiplikation in deinem Post?

EDIT: naja, Problem scheint ja schon gelöst...
 
Zuletzt bearbeitet:
Zeig uns mal den kompletten Code. Also inklusive operator * und Kopierkonstruktor. Dein operator *= ist außerdem etwas seltsam implementiert. Normalerweise nutzt man in der Implementierung von operator * den operator *= und nicht umgekehrt. Also:

Code:
class Bruch
{
public:
	Bruch(int z, int n) : zaehler(z), nenner(n)
	{
		cout << "Konstruktor" << endl; 
	}
	
	Bruch( const Bruch& rhs ) : zaehler( rhs.zaehler ), nenner( rhs.nenner )
	{
		cout << "Kopierkonstruktor" << endl;
	}
	
	Bruch& operator *= ( const Bruch& rhs )
	{
		zaehler *= rhs.zaehler;
		nenner *= rhs.nenner;
		
		return *this;
	}
	
	Bruch operator * ( const Bruch& rhs ) const
	{
		Bruch result( *this );
		result *= rhs;
		return result;
		
		// könnte man noch kürzen schreiben:
		//return Bruch( *this ) *= b;
	}
	

private:
	int zaehler;
	int nenner;

};
Ergänzung ()

Holt schrieb:
[...]und mache
Bruch b3 (b1 * b2);
dann hast Du nur 3 Aufrufe zwei Deines Konstruktors und einen des Kopierkonstruktors.

An dieser Stelle möchte ich mal anmerken, daß

Bruch b3 (b1 * b2);

lediglich ein andere Schreibweise für

Bruch b3 = b1 * b2;

ist. In beiden Fällen wird b3 über den Kopierkonstruktor erstellt.
 
Zuletzt bearbeitet:
sash2k2 schrieb:
Fehlt da nicht noch der Code für die reine Multiplikation in deinem Post?
Was heißt reine Multiplikation? Bruch * Int?

antred schrieb:
Zeig uns mal den kompletten Code. Also inklusive operator * und Kopierkonstruktor. Dein operator *= ist außerdem etwas seltsam implementiert. Normalerweise nutzt man in der Implementierung von operator * den operator *= und nicht umgekehrt.
Kopierkonstruktor brauche ich ja atm nicht zum Verstehen, denke ich. operator* habe ich nicht, die Multiplikation funktioniert auch ohne.

Ich denke mir fehlt einfach das Verständniss für die Zeile:
this* = *this * b;
In dieser müsste ja dann der vierte Aufruf stattfinden, oder etwa im Funktionskopf?

EDIT:
antred schrieb:
An dieser Stelle möchte ich mal anmerken, daß
Bruch b3 (b1 * b2);
lediglich ein andere Schreibweise für
Bruch b3 = b1 * b2;
ist. In beiden Fällen wird b3 über den Kopierkonstruktor erstellt.
Oh Mann, natürlich! Kopierkonstruktor. Das liegt in keiner Weise am *this. Tja, das ist das tolle am Quellcode, da passieren Dinge, die da gar nicht explizit stehen :D Danke euch allen!

EDIT2:
Heißt das dann, dass es immer der Kopierkonstruktor ist, wenn da steht:
Bruch b5 = *whatever* ?
 
Zuletzt bearbeitet von einem Moderator:
Und jetzt noch was. Wenn du mit Optimierungen übersetzt, kann es sogar passieren, daß am Ende weniger Kopierkonstruktoraufrufe rauskommen, als man erwarten würde, denn es ist dem Compiler freigestellt, sogenannte "temporaries" wegzuoptimieren, SELBST WENN DER KOPIERKONSTRUKTOR NEBENEFFEKTE HAT (und somit der Programmablauf durch das Wegoptimieren der Kopierkonstruktoren verändert würde)!

http://en.wikipedia.org/wiki/Copy_elision
 
Dann kann man sich damit quasi auch noch das Programm zerschiessen? Sind ja super Möglichkeiten! :D
EDIT: Bitte frage am Ende meines letzten Posts nicht überlesen! :)
 
derBobby schrieb:
EDIT2:
Heißt das dann, dass es immer der Kopierkonstruktor ist, wenn da steht:
Bruch b5 = *whatever* ?


Wenn *whatever* ein Bruch ist oder ein Ausdruck, der als Ergebnis eine Bruch-Instanz liefert, dann ja. Wenn auf der rechten Seite ein Ausdruck steht, der keine Bruch-Instanz liefert, dann wird der Compiler versuchen, einen Kandidaten zu finden, mit dem das Ding auf der rechten Seite in einen Bruch-Instanz konvertiert werden kann (nicht als explicit deklarierte Bruch-Konstruktoren mit einem Argument oder einen geeigneten cast-Operator).
Ergänzung ()

derBobby schrieb:
Dann kann man sich damit quasi auch noch das Programm zerschiessen? Sind ja super Möglichkeiten! :D

Nur wenn deine Kopierkonstruktoren noch Nebeneffekte haben (also mehr machen, als nur eine Kopie zu erstellen) und du dich absolut auf diese Nebeneffekte verläßt. Das wäre dann aber ohnehin bad practice. ;)
 
*this = *this * b;

Was davon ruft dann aber den Konstruktor auf, bzw. muss umgewandelt werden? Hinter dem *this steht der erste Bruch vor dem Operator *= und b ist ja auch schon ein Bruch. Also dürfte es doch keinen Aufruf geben?

EDIT: Kopierkonstruktor der Vollständigkeit halber eingebaut. Code siehe oben.
 
Zuletzt bearbeitet von einem Moderator:
derBobby schrieb:
*this = *this * b;

Was davon ruft dann aber den Konstruktor auf, bzw. muss umgewandelt werden? Hinter dem *this steht der erste Bruch vor dem Operator *= und b ist ja auch schon ein Bruch. Also dürfte es doch keinen Aufruf geben?

Moment, ich sprach von

Bruch b3 = b1 * b2;

! Folgendes:

Bruch b3;
b3 = b1 * b2;


Wäre schon wieder ein völlig andere Geschichte. ;)

Hier würde b3 erst mal über einen parameterlosen Defaultkonstruktor erstellt (es sei denn, deine Klasse hat keinen ... dann würde sich die Zeile Bruch b3; überhaupt nicht kompilieren lassen). Anschließend würde dann das Ergebnis von b1 * b2 berechnet. Diese Berechnung gibt eine "temporary" zurück, die mit dem Kopierkonstruktor erstellt wird. Diese temporary wird dann mit dem Zuweisungsoperator (operator =) an b3 zugewiesen.
Ergänzung ()

derBobby schrieb:
EDIT: Kopierkonstruktor der Vollständigkeit halber eingebaut. Code siehe oben.

Ich würde dir raten, zaehler und nenner nicht im Rumpf des Kopierkonstruktors sondern, wie du es bei dem anderen Konstruktor auch getan hast, mit einer Initialisierungsliste zu initialisieren.
 
Also schön, neuer Gedanke um meine Frage richtig zu formulieren.

Code:
Bruch b1(1,1);
Bruch b2(1,1);

b1 = b2 * b2; <- diese Zeile ruft einmal Konstruktor und einmal Destruktor auf.

Die Frage ist jetzt halt, was genau in der Funktion löst den Konstruktor aus, weil *this im Prinzip ein Bruch ist, genauso wie b?:

Code:
const Bruch & Bruch::operator *= (const Bruch &b){
[INDENT]return *this = *this * b;[/INDENT]
}
 
Zuletzt bearbeitet von einem Moderator:
derBobby schrieb:
operator* habe ich nicht, die Multiplikation funktioniert auch ohne.


Kann nicht sein. Irgend wo in deinem Programm muß ein operator * für 2 Operanden vom Typ Bruch definiert sein, sonst würde sich dein Code nicht kompilieren lassen. Und eben diesen operator * solltest du jetzt mal rausrücken, denn von der Implementierung hängt ab, was bei

Bruch a( 1, 2 );
Bruch b( 3, 4 );

Bruch c = a * b;

eigentlich so abläuft. Notfalls schmeiß halt mal deinen Debugger an und steppe durch a * b; nur um zu sehen, wo dieser operator eigentlich definiert ist.
 
Ich könnt mich schlagen! Wie blind kann ein Einzelner sein?

Code:
inline Bruch Bruch::operator * (const Bruch &b) const
{
return Bruch(zaehler * b.zaehler, nenner * b.nenner);
}

*AufKnienRumRutschUndUmVerzeihungBitt* :freak:
Und auf einmal ist es sogar mir klar. Bevor ich hier gepostet hab, bin ich den Code zwei Mal durch und habe nach der Operator-Funktion gesucht. Zwei Mal überlesen...

Kann man die Fkt auch so gestalten, dass kein weiterer Konstruktor notwendig ist?
 
Nee. ;) Ich würde den operator * zwar so schreiben, daß er unter der Haube letztendlich die Implementierung vom operator *= nutzt ... aber ist Geschmackssache. Aber um den Konstruktor für die temporary kommst du nicht umhin. Aber keine Sorge, moderne Compiler sind beim Optimieren ziemlich aggressiv, und so manche temporary wird in einem optimierten Build einfach wegfallen.
 
Doch, Du kannst einen Konstruktor schreiben, der zwei Brüche aufnimmt und diese multipliziert:

Bruch( const Bruch& mb1, const Bruch& mb2 )
{
zaehler = mb1.zaehler * mb2.zaehler;
nenner = mb1.nenner * mb2.nenner;
}

Wenn Du das aber auch für z.B. die Division machen willst, dann hast Du ein Problem.
 
Das ändert nicht das geringste an der Zahl der Konstruktordurchläufe, die für den operator * stattfinden. Wenn du das anders siehst, erklär's mir bitte.
 
antred, wo wird denn bei der Lösung mit dem "Bruch( const Bruch& mb1, const Bruch& mb2 )" Konstruktor noch ein weiterer Konstruktor aufgerufen?
 
Holt schrieb:
antred, wo wird denn bei der Lösung mit dem "Bruch( const Bruch& mb1, const Bruch& mb2 )" Konstruktor noch ein weiterer Konstruktor aufgerufen?

Um etwaige Mißverständnisse auszuschließen, es geht darum, derBobby's Implementierung für den "operator *", welche zur Zeit so aussieht:

Code:
inline Bruch Bruch::operator * (const Bruch &b) const
{
	return Bruch(zaehler * b.zaehler, nenner * b.nenner);
}

so zu gestalten, daß der Kopier-Konstruktor für die von der Methode zurückgegebenen temporary entfällt. Das ist schlicht und ergreifend nicht möglich. Die von dir vorgeschlagene Implementierung für den "operator *":

Code:
Bruch( const Bruch& mb1, const Bruch& mb2 )
{
	zaehler = mb1.zaehler * mb2.zaehler;
	nenner = mb1.nenner * mb2.nenner;
}

inline Bruch Bruch::operator * (const Bruch &b) const
{
	return Bruch( *this, b );
}

erzeugt diesen temporary genauso wie jede andere mögliche (korrekte) Implementierung von "operator *".
 
Zuletzt bearbeitet:
Wo wird denn bitte bei dem Konstruktor ein Temporary erzeugt:

Bruch( const Bruch& mb1, const Bruch& mb2 )
{
zaehler = mb1.zaehler * mb2.zaehler;
nenner = mb1.nenner * mb2.nenner;
}

Den * operator nutze ich nicht, denn mit dem kommt man um einen temporary nicht herum, den es aber zu verhindern ging, so wie ich das verstanden habe.

Aber egal, ich klinge mich jetzt hier aus.
 
Holt, DerBobby's Frage lautete doch ganz klar:

Kann man die Fkt auch so gestalten, dass kein weiterer Konstruktor notwendig ist?

Und da er nur ein paar Zeilen über dieser Frage seine Implementierung vom operator * postete, ist es doch nicht unvernünftig, anzunehmen daß er mit "die Fkt" den operator * meinte, oder?
 
Zurück
Oben