[gelöst]Unterschied Typumwandlungskonstrutor zu Allgemeinen Konstruktor
-
Guten Abend,
ich lese mich gerade in den Typumwandlungskontruktor ein und verstehe nicht ganz wie man diesen von einem Allgemeinen Konstruktor unterscheiden kann. Bei einem Kopierkonstruktor z.B. wird ja eine Referenz auf dieselbe Klasse als Argument mit übergeben und daran erkennt man ihn dann, aber wie erkennt der Compiler einen Typumwandlungskonstruktor? Wenn es wirklich zwei unterschiedliche Dinge sind dann müssten ja auch die Signaturen unterschiedlich sein oder?
G hibbes
-
Das ist an sich nichts besonderes: Bei Zuweisungen wird automatisch geguckt, ob ein passender Konstruktor mit genau einem Argument vorhanden ist. Eigentlich genau wie beim Kopierkonstruktor, nur mit einem anderen Argumententyp.
Falls du die automatische Umwandlung nicht wünscht, musst du den entsprechenden Konstruktor
explicitmachen.
-
SeppJ schrieb:
Das ist an sich nichts besonderes: Bei Zuweisungen wird automatisch geguckt, ob ein passender Konstruktor mit genau einem Argument vorhanden ist. Eigentlich genau wie beim Kopierkonstruktor, nur mit einem anderen Argumententyp.
Genauer: mit min. einem Argument und max. einem Argument welches keinen Default-Wert hat (wobei auch alle Argumente einen Default-Wert haben dürfen).
Dasselbe gilt im Übrigen auch für den Copy-Ctor.
Und der Default-Ctor darf auch ruhig Argumente haben, so lange alle einen Default-Wert haben.Beispiel:
class foo { public: static foo f; foo(int a, int b = 123) {} // A foo(void* p, void* p2 = 0) {} // B (auch "p" dürfte einen default-wert haben, nur dann hätten wir mit C zwei default-konstruktoren, was nicht OK ist) foo(foo const& foo = f, int* p = 0) {} // C - copy ctor + default ctor }; foo foo::f; // calls C EDIT: das ist natürlich unfug, da hier C mit dem default-wert aufgerufen würde, // und der default-wert ist f. also genau das objekt welches initialisiert werden soll. henne/ei und so. // syntaktisch OK, aber totaler unfug und zur laufzeit undefiniertes verhalten. // man müsste f also mit einem anderen konstruktor initialisieren, dann wäre es OK void bar(foo f) { } foo bar() { foo f; // calls C as default-ctor bar("seppdepp"); // calls B return 1; // calls A } int main() { foo f(bar()); // may call C as copy-ctor (may be elided via RVO) foo f2 = f; // calls C as copy-ctor }EDIT: siehe code
-
Danke für die wirklich ausführlichen Erklärungen. Ein Konstruktoraufruf macht mich erstmal stutzig, aber is ja auch schon spät

foo bar() { foo f; // calls C as default-ctor bar("seppdepp"); // calls B return 1; // calls A: nicht doch C weil copy und default hier eins sind? }Wird hier nicht bei dem Return der Copy-Ctor aufgerufen, bei meinen Tests wird der aufgerufen wenn das Objekt der Klasse per Kopie übergeben wird und wenn es eine Rückgabe vom Typ der Klasse gibt?
G hibbes
-
Naja, ist ja nicht schwer auszuprobieren:
#include "stdafx.h" #include <iostream> class foo { public: char const* m_a; foo(char const* a) { m_a = a; std::cout << "conversion ctor: a = " << m_a << "\n"; } foo(foo const& other) { m_a = other.m_a; std::cout << "copy ctor: a = " << m_a << "\n"; } void use() { std::cout << "use: a = " << m_a << "\n"; } void main_checkpoint() { std::cout << "main checkpoint: a = " << m_a << "\n"; } }; foo test_implicit() { return "implicit conversion"; } foo test_explicit() { return foo("explicit conversion"); } foo test_named_value() { foo f("named return value"); return f; } foo test_named_used_value() { foo f("named & used return value"); f.use(); return f; } foo nested_direct_helper(char const* a) { return foo(a); } foo nested_named_used_helper(char const* a) { foo f(a); f.use(); return f; } foo test_nested_direct() { return nested_direct_helper("nested direct"); } foo test_nested_named_used() { return nested_named_used_helper("nested named used"); } foo test_nested_2x_named_2x_used() { foo f(nested_named_used_helper("nested 2x named 2x used")); f.use(); return f; } int main() { { foo f(test_implicit()); f.main_checkpoint(); std::cout << "\n\n"; } { foo f(test_explicit()); f.main_checkpoint(); std::cout << "\n\n"; } { foo f(test_named_value()); f.main_checkpoint(); std::cout << "\n\n"; } { foo f(test_named_used_value()); f.main_checkpoint(); std::cout << "\n\n"; } { foo f(test_nested_direct()); f.main_checkpoint(); std::cout << "\n\n"; } { foo f(test_nested_named_used()); f.main_checkpoint(); std::cout << "\n\n"; } { foo f(test_nested_2x_named_2x_used()); f.main_checkpoint(); std::cout << "\n\n"; } }Mit VC10 mit Debug Einstellungen compiliert:
conversion ctor: a = implicit conversion main checkpoint: a = implicit conversion conversion ctor: a = explicit conversion main checkpoint: a = explicit conversion conversion ctor: a = named return value copy ctor: a = named return value main checkpoint: a = named return value conversion ctor: a = named & used return value use: a = named & used return value copy ctor: a = named & used return value main checkpoint: a = named & used return value conversion ctor: a = nested direct main checkpoint: a = nested direct conversion ctor: a = nested named used use: a = nested named used copy ctor: a = nested named used main checkpoint: a = nested named used conversion ctor: a = nested 2x named 2x used use: a = nested 2x named 2x used copy ctor: a = nested 2x named 2x used use: a = nested 2x named 2x used copy ctor: a = nested 2x named 2x used main checkpoint: a = nested 2x named 2x usedHier wird also noch ein paar mal der Copy-Ctor aufgerufen. Bei den ganz einfachen Beispielen "fehlt" er allerdings schon.
Mit Release Einstellungen sieht es dann so aus:
conversion ctor: a = implicit conversion main checkpoint: a = implicit conversion conversion ctor: a = explicit conversion main checkpoint: a = explicit conversion conversion ctor: a = named return value main checkpoint: a = named return value conversion ctor: a = named & used return value use: a = named & used return value main checkpoint: a = named & used return value conversion ctor: a = nested direct main checkpoint: a = nested direct conversion ctor: a = nested named used use: a = nested named used main checkpoint: a = nested named used conversion ctor: a = nested 2x named 2x used use: a = nested 2x named 2x used use: a = nested 2x named 2x used main checkpoint: a = nested 2x named 2x usedWie man sieht wird hier kein einziges mal der Copy-Ctor aufgerufen. Alle Aufrufe wurden wegoptimiert.
Die Regeln die das ganze laut Standard erlauben nennen sich RVO ("return value optimization") und NRVO ("named return value optimization").
-
p.S.: vielleicht hab ich auch deine Frage falsch verstanden.
hibbes schrieb:
return 1; // calls A: nicht doch C weil copy und default hier eins sind?Da steht 1 (eins), und das ist ein Integer-Literal (eine Konstante).
Da ein Integer kein "foo" ist, muss irgendwie konvertiert werden, und das kann nur der Konvertierungskonstruktor "A".Es darf danach zusätzlich der Copy-Ctor "C" aufgerufen werden, muss aber nicht. Und wird in so einfachen Fällen meist auch nicht.
-
Vielen vielen lieben Dank,
da hast du dir ja echt Mühe gegeben einem C++ Anfänger was zu erklären. Das bei dem return 1 ja einen Typumwandlung stattfindet hatte ich bei meiner Frage nicht bedacht und nun macht dein Beispiel auch Sinn.Ich denke deine anschaulichen Beispiele werden auch Anderen eine große Hilfe sein, danke nochmal. Du solltest C++ Dozent werden

G hibbes