Frage - Klasseninitialisierung
-
Wenn ich sowas mache:
class Foo { .... ...... } int main() { int a; Foo temp=a; //!!! return 0; }Geht das doch nur wenn der Konstruktor von "Foo" nur ein Argument hat. Sollte der Konstruktor von "Foo" mehr als ein Argument haben dann geht das doch nur noch so?:
..... int main() { int a; float c; Foo temp=foo(a,c,......); //!!! return 0; }Also ist der einzige Unterschied zwischen "Foo temp=a"[1] und "Foo temp=Foo(a,c,.....)"[2], das bei:
[1] Foo nur ein Argument im Konstruktor haben kann, sont --> Fehlermeldung. ???
[2] Foo kann soviele Argumente haben wie es möchte?Und sonst gibts ja keinen Unterschied mehr, oder? Bei beiden wird eine "copy initialization" durchgeführt. Also erstellen eines "Foo temp" Objekts, und diese wird dann übergeben und anschließend gelöscht. Oder der Compiler optimiert den Code so, das es zum Schluss wie eine ganz normale Initialisierung wie mit einem Konstruktor ist?
Ist das alles korrekt, hab ichs richtig verstanden?MfG
Stromberg
-
Foo temp=a; //entspricht: Foo temp(a);Es wird kein temporäres Objekt angelegt, sondern direkt der Konstruktor mit dem passenden Argument aufgerufen.
Bei mehreren Argumenten geht das so:Foo temp(a, b, c, ...);Deine Variante ist unüblich und es ist sogar möglich das der Kompiler da den Copy-Ctor-Aufruf wegoptimiert. Ich bin mir nicht ganz sicher, ob das erlaubt ist. Der MSVC macht das auf jeden Fall so.
Gruß
Don06
-
Ja, ich weiß schon, normalerweiße ruft man ganz normal den Konstruktor auf. Aber "copy initializations" sollen ja teilweiße schneller sein als "direct initzializations":
http://fara.cs.uni-potsdam.de/~kaufmann/?page=GenCppFaqs&faq=copyvsdirect#Answ
Und deshalb wollte ich mich jetzt mal mit den "copy initzializations" beschäftigen.So noch eine Frage, wenn man vor seinen Konstruktor "explicit" schreibt, dann darf man ja nur noch explizite Initialisierungen machen, und keine impliziten mehr. Also sowas würde nicht mehr gehen "Foo temp=a", sondern nur noch "Foo temp=Foo(a)", aber warum eigentlich? Es ist doch egal wie ichs schreibe oder? Und sollte "Foo" mehr als ein Argument haben, dann kann ich ja sowieso nur noch "Foo temp=Foo(....)" machen, weil bei "Foo temp=a" würde ja sowieso eine Compilererror kommen? Kann mir jemand das mit dem "explicite" nochmal genau erklären?
MfG
Stromberg
-
Im Internet hab ich das hier gefunden:
Das Schlüsselwort explicit wird in nicht verwaltetem C++ dazu verwendet, einen Konstruktor zu deklarieren, der nur ein Argument akzeptiert, jedoch explizit aufgerufen werden muss.
Häää nur ein Argument? Ich hab gedacht genau das sollte verhindert werden. Also ich chekc grad gar nix mehr!?! Was ist den auch bitte jetzt der Unterschied zwischen einem expliziten Konstruktor aufruf, und einem impliziten Konstruktoraufruf?
MfG
Stromberg
-
Stromberg schrieb:
Aber "copy initializations" sollen ja teilweiße schneller sein als "direct initzializations":
http://fara.cs.uni-potsdam.de/~kaufmann/?page=GenCppFaqs&faq=copyvsdirect#AnswDas steht dort nicht. Abgesehen davon ist die Erklärung u.a. hinsichtlich der Eliminierung von Temporaries auch fehlerhaft.
Im Prinzip kann man sich sehr kurz fassen (bezogen auf die Initialisierung von Klassenobjekten):
Direkte Initialisierung konstruiert ein Objekt unter direkter Benutzung der initialisierenden Ausdrücke, d.h., es wird nach einem passenden Konstruktor gesucht und dieser aufgerufen (der Aufruf selbst bedeutet für Argumente, die nicht direkt an eine Referenz gebunden werden, Copy-Initialisierung und kann so temporäre Objekte ins Spiel bringen).
Copy-Initialisierung konstruiert ein Objekt unter Benutzung des in den Zieltyp konvertierten initialisierenden Ausdrucks (wenn der Ausdruck schon den richtigen Typ hat, haben wir es mit der identischen Konvertierung zu tun, die nichts tut - dieser Fall verhält sich also wie direkte Initialisierung).Welche der beiden Initialisierungsformen genutzt wird, hängt vom Kontext ab, in dem die Initialisierung auftritt - und im Grunde kann man das nur auswendig lernen (ist aber nicht sehr viel).
Unter diesen Bedingungen ist es leicht zu verstehen, wann etwa explicit eine Rolle spielt: explicit verhindert die Verwendung des betreffenden Konstruktors in impliziten Konvertierungen - spielt also nur bei Copy-Initialisierung eine Rolle.
Zudem bedeutet Copy-Initialisierung nicht zwingend (auch wenn das zu 99.9% der Regelfall ist) die Verwendung des Copy-Konstruktors:
std:::auto_ptr<foo> f(); std::auto_ptr<foo> p = f();Das ist klassische Copy-Konstruktion (mit identischer Konvertierung), aber kein Copy-Konstruktor ist hier involviert (nebenbei bediente sich hier auch die direkte Initialisierung eines temporären Objektes (bei der Copy-Initialisierung des betreffenden Konstruktorparameters), das nicht eliminiert werden kann).
De Eliminierung von temporären Objekten wiederum ist nicht von der Form der Initialisierung abhängig und es kommt nicht darauf an, ob des betreffende temporäre Objekt implizit oder explizit erzeugt wurde, es muss lediglich ungebunden sein und als Kopiervorlage dienen:
Foo t(Foo(Foo(Foo(a))));Der Compiler darf hier ganz nach Belieben alle Kopien eliminieren und t direkt aus a konstruieren (sofern Foo kein auto_ptr o.ä. ist
, denn Copy-Elision betrifft die Eliminierung von Copy-Konstruktor-Aufrufen (und zugehörigen Destruktoren) - und der Copy-Konstruktor wird hier ja nicht genutzt, wie oben ausgeführt wurde).
-

**150px groove #FF00FFhidden
-
Mh, also igrnedwie hab ich da jetzt nur Bahnhof verstanden. Kann mir das nicht igrendjemand "Idiotensicher" erklären? Da waren so viele komplizierte Wörter (für mich) im obrigen Text...kp...
Ich weiß irgendwie immer noch nicht was das Schlüsselwort "explicit" bedeutet, und wie man einen Konstruktor "explicit" oder "implicit" macht hab ich glaub ich au net verstanden, oder ist das hier:
Foo t=b; //implizit? Geht nur wenn Foo EIN ARGUMENT HAT..?
Foo t=Foo(b); //explizit? Foo kann ein ODER MEHR ARGUEMNTE HABEN, ist egal.Sry, das ichs net verstanden habe. Is wahrscheinlich sogar eine gute Erklärung, aber kp....ich verstehs einfach nicht :(:(:(
MfG
Stromberg
-
Stromberg schrieb:
Mh, also igrnedwie hab ich da jetzt nur Bahnhof verstanden. Kann mir das nicht igrendjemand "Idiotensicher" erklären? Da waren so viele komplizierte Wörter (für mich) im obrigen Text...kp...
Ich weiß irgendwie immer noch nicht was das Schlüsselwort "explicit" bedeutet, und wie man einen Konstruktor "explicit" oder "implicit" macht hab ich glaub ich au net verstanden, oder ist das hier:
Foo t=b; //implizit? Geht nur wenn Foo EIN ARGUMENT HAT..?
Foo t=Foo(b); //explizit? Foo kann ein ODER MEHR ARGUEMNTE HABEN, ist egal.Sry, das ichs net verstanden habe. Is wahrscheinlich sogar eine gute Erklärung, aber kp....ich verstehs einfach nicht :(:(:(
MfG
Strombergmit diesen Deklarationen
struct Foo { Foo(int); explicit Foo(float); Foo(const Foo&); }; int i; float f;Betrachten wir zunächst
Foo x(i);Das ist direkte Initialisierung des Objektes x mit dem Ausdruck i. Direkte Initialisierung bedeutet,wir suchen uns den am besten zu den Argumenten passenden Konstruktor von Foo (hier Foo::Foo(int) )und rufen diesen mit den Argumenten auf. Ob der so gewählte Konstruktor explicit ist oder nicht, ist dabei unerheblich:
Foo x(f);Gleicher Fall, nur dass wir den Konstruktor Foo::Foo(float) benutzt.
Foo x = i;Das ist Copy-Initialisierung des Objektes x mit dem Ausdruck i. Copy-Initialisierung impliziert, dass wir irgendwas kopieren - und kopieren kann man üblicherweise nur zwischen Objekten gleichen Typs. Um also das Objekt x durch Kopieren zu konstruieren, benötigen wir erst einmal eine Kopiervorlage vom Typ Foo - wir müssen also den Wert des Ausdrucks i in einen Wert des Typs Foo umwandeln. Das geschieht durch eine sogenannte implizite Konvertierungssequenz. Von der großen Menge Regeln, die sich mit diesen Konvertierungssequenzen beschäftigen, muss uns hier nur interessieren, dass eine solche Sequenz einen Konstruktoraufruf enthalten kann. In diesem speziellen Falle, geht es also darum, ein Objekt vom Typ Foo zu erschaffen unter Verwendung des Ausdrucks i - das kann nur durch die Erstellung eines temporären Objektes mittels des Konstruktors Foo::Foo(int) geschehen. Dieser Aufruf des Konstruktors als auch die Erstellung des temporären Objektes geschehen implizit - ohne den Typ des Ausdrucks i zu kennen, können wir nicht sagen ob die Initialisierung (von x) ein temporäres Objekt benötigt oder nicht. Sollte aber der für eine solche Konvertierungssequenz benötigte Konstruktor explicit sein, dann ist der betreffende Code fehlerhaft und der Compiler muss es diagnostizieren:
Foo x = f;Hier müsste der Konstruktor Foo::Foo(float) implizit aufgerufen werden, um den Wert des Ausdrucks f in einer Wert vom Typ Foo zu konvertieren, das ist aber nicht erlaubt, da dieser Konstruktor als explicit deklariert wurde.
Foo x = Foo(i);Hier haben wir tatsächlich zwei Initialisierungen:
Das Objekt x wird copy-initialisiert mit dem Wert des Ausdrucks Foo(i).
Foo(i) selbst erstellt ein temporäres Objekt, welches direkt initialisiert wird. Wie das abläuft: s.o.Foo x = Foo(f);Wieder zwei Initialisierungen, der Aufruf des expliziten Konstruktors Foo::Foo(f) geschieht nicht während einer Konvertierungssequenz, sondern bei der Konstruktion eines temporären Objekts, das als solches ganz explizit im Code auftaucht: das ist folglich erlaubt.
Foo(i) ist ein functionstyle-cast - alle in Frage kommenden Castformen (C-style und static_cast) initialisieren das entsprechende temporäre Objekt direkt.
-
Es muss nicht unbedingt nur ein Argument sein. Man kann auch mehrere Argumente haben, wenn es fuer die zusaetlichen defaultwerte gibt. Zumindest mein gcc hat folgendes geschluckt:
class Foo { float f_; public: explicit Foo(int i, float f2 = 1.5) {f_ = i + f2;} Foo(float f, float f2 = 2.5) : f_(f+f2) {} }; int main() { float f = 2.5; int i = 12; Foo f1(f); Foo f2 = f; Foo f3(i); Foo f4 = i; }
-
pumuckl schrieb:
Es muss nicht unbedingt nur ein Argument sein. Man kann auch mehrere Argumente haben, wenn es fuer die zusaetlichen defaultwerte gibt. Zumindest mein gcc hat folgendes geschluckt:
Wahr, allerdings als solches kein Widerspruch zum zuvor ausgeführten. Allerdings hat mich dein Beispiel daran erinnert, dass meine Erklärung für explicit fehlerhaft war:
Foo f4 = i;Das geht hier, aber der Konstruktor, der aufgerufen wird, ist Foo::Foo(float, float). Dagegen wird f3 durch den Konstruktor Foo::Foo(int, float) konstruiert.
Korrektur Nr. 1: Die Auswahl eines explicit Konstruktors während einer impliziten Konvertierung stellt keinen Fehler dar - ein solcher Konstruktor ist von vornherein kein Konvertierungskonstruktor und wird gar nicht erst berücksichtigt (und es ist dann ggf. ein Fehler, dass keine Konvertierungsmöglichkeit gefunden wird). Der Unterschied wird in dem Beispiel deutlich: Foo::Foo(int, float) wäre eigentlich die bessere Überladung, und würde gewählt werden, wenn explicit erst nach Überladungsauflösung berücksichtigt würde. So wird der Konstruktor Foo(int, float) aber gar nicht erst Teil des Überladungssets.
Korrektur Nr. 2: explicit verhindert nicht nur die Benutzung bei impliziter Konvertierung - ein solcher Konstruktor darf überhaupt nur dort verwendet werden, wo die syntaktische Form der direkten Initialisierung gewählt wird. Das wirkt sich primär bei expliciten Kopierkonstruktoren aus:zum Nachdenken, welche der Folgenden Initialisierungen ist fehlerhaft:
struct Foo { Foo(); Foo(int); explicit Foo(const Foo&); }; struct Bar { Bar(); explicit Bar(int); Bar(const Bar&); }; int main() { int i = 0; Foo x; Bar y; Foo x1(i); Foo x2(x); Foo x3 = i; Foo x4 = x; const Foo& x5 = i; const Foo& x6 = x; Bar y1(i); Bar y2(y); Bar y3 = i; Bar y4 = y; const Bar& y5 = i; const Bar& y6 = y; }
-
Sry, das ich erst wieder so spät schreibe, hat grad keine Zeit. Mh also was ich bisher mal sicher gecheckt habe ist, das wenn der Konstruktor "explicite" ist, dann gehen nur noch solche "copy initializations" --> Foo temp=Foo(20); und solche nicht mehr --> Foo temp=20;
#include <iostream> using namespace std; class Foo { public: explicit Foo(int save_); ~Foo(); private: int m_save; }; Foo::Foo(int save_) :m_save(save_) { } Foo::~Foo() { } int main() { Foo X1=Foo(20); //GEHT Foo X2=20; //COMPILIERERFEHLER return 0; }Aber ich verstehe trotzdem den Unterschied zwischen "Foo X1=Foo(20);" und "Foo X2=20" nicht. Okay, ich weiß dass das erstere [Foo X1=Foo(20);] "explizit" ist, und das letztere [Foo X2=20;] "implizit" ist. Soweit okay, aber die machen doch beide das gleiche oder? "Foo X2=20;" deklariert ein "temp(20)" Objekt (oder halt irgend n anderer Name...), und übergibt diese über den Kopierkonstruktor (an X2).
Ja und "Foo X1=Foo(20)" macht doch genau das selbe? Es wird wieder ein "temp(20)" Objekt erstellt, und dann über den Kopierkonstruktor übergeben (an X1).
Der einzige Unterschied ist doch also nur, das ich bei "Foo(20)" noch mehr Arguemnte übergeben kann, also z.B. "Foo X1=Foo(20,4,5,29,4);". [Also der Konstruktor von der Klasse Foo muss natürlich entsprechend viele Parameter haben....is ja selbstverständlich.]
Aber irgendwie blick ich so den großen Unterschied noch nicht?! Oder ist es gar nicht so wie ich gesagt habe?MfG
StrombergPS: Ich schau mir gleich mal die Übung an.
-
Der Unterschied liegt zwischen "explizit" und "implizit" - im ersten Fall sagst du dem Compiler, daß du ein Foo-Objekt mit dem int-Ctor angelegt haben willst, im letzteren Fall sagst du ihm, daß er irgendwie versuchen soll, den int-Wert in ein Foo umzuwandeln (und dafür stehen ihm nur implizite Umwandlungen zur Verfügung, also nicht der angegebene Ctor).
-
CStoll schrieb:
...und dafür stehen ihm nur implizite Umwandlungen zur Verfügung...
Und was wären das das dann für Umwandlungen? Wie sieht das dann aus, was macht dann der Compiler mit dem Code?
Ist "implizit" schneller, oder ist "explicit" schneller? Weil wenn z.B. "explicit" schneller wäre, dann könnte man ja nur imemr das verwenden...oder?
MfG
Stromberg
-
Stromberg schrieb:
CStoll schrieb:
...und dafür stehen ihm nur implizite Umwandlungen zur Verfügung...
Und was wären das das dann für Umwandlungen? Wie sieht das dann aus, was macht dann der Compiler mit dem Code? ...
Implizite Umwandlungen bekommst du z.B. wenn du einen short-Wert einem int-Parameter übergibst. Explizit bei einem Konstruktor heißt, das dieser keine impliziten Umwandlung vornehmen darf. Sprich wenn du einer Klasse nur einem expliziten Konstruktor mit einem int-Wert als Parameter gibst, kannst du diese nicht mit einem short-Wert konstruieren.
Explizite Konstruktoren sind in der Regel den impliziten vorzusehen wenn es um Typsicherheit geht.
cu André
-
Achso!!!! Jetzt wird mir einges klar, löl 2 Tage tue ich rum bis ichs checke. Okay, dann versteh ichs jetzt, is ja eigentlich ganz einfach.
Mh, aber nochmal zur Schreibweise, ich weiß ja jetzt dass das hier... -->class Foo; int main() { int temp Foo X=Foo(temp); //...explizit ist! return 0; }und das hier:
class Foo; int main() { int temp; Foo X=temp; //...implizit ist! return 0; }1. Gibt es für "implizit" noch eine Andere Form? Weil wie schön des öfteren gesagt, verstehe ich das sonst nicht, weil sonst kann man mit einer "implizit" copy initialization ja nur einen Parameter abdecken. Daraus folgt ja dann wohl, die Klasse die "implizit" copy initializiert wird, darf nur ein Argument im Parameter haben, bzw. müssen sonst die anderen alles einen default Parameter haben. Ist das korrekt? Oder gibts noch eine andere "implizit Form"?
2.CStoll schrieb:
...im letzteren Fall sagst du ihm, daß er irgendwie versuchen soll, den int-Wert in ein Foo umzuwandeln...
Das ist ja eigentlich das was "asc" angesprochen hat. Mit dem "short" an ein "int" übergeben. Findet hier also so eine art "static_cast" statt? Jetzt mal aus reinem Interesse, wie funktioniert das eigentlich im "hintergrund"? genauso wie beim expliziten, das erst ein "temp Objekt" erstellt wird, das dann irgendwie mit ner Umwandlung (static_cast) übergeben wird....? Weiß da jemand wie das gemacht wird?
3. Eigentlich verwendet man copy initializations, ob "implizit" oder "explizit" doch eher selten? Meistens nimmt man doch immer noch den Konstruktor oder? Zumal der doch auch viel schneller ist (direct_initialization).Dankeschön schon mal im Voraus.
MfG
Stromberg
-
Stromberg schrieb:
1. Gibt es für "implizit" noch eine Andere Form? Weil wie schön des öfteren gesagt, verstehe ich das sonst nicht, weil sonst kann man mit einer "implizit" copy initialization ja nur einen Parameter abdecken. Daraus folgt ja dann wohl, die Klasse die "implizit" copy initializiert wird, darf nur ein Argument im Parameter haben, bzw. müssen sonst die anderen alles einen default Parameter haben. Ist das korrekt? Oder gibts noch eine andere "implizit Form"?
Impliziter Konstruktoraufruf ist immer dann möglich, wenn es nur ein Parameter (+beliebig viele weitere mit Defaultargumenten) gibt. Eine Implizite Konvertierung kann aber mit beliebig vielen Parametern stattfinden (z.B. wenn der dritte Parameter ein int verlangt, du aber ein short übergibst).
Stromberg schrieb:
2. Das ist ja eigentlich das was "asc" angesprochen hat. Mit dem "short" an ein "int" übergeben. Findet hier also so eine art "static_cast" statt?...
Ja, der Compiler darf glaube ich auch 2 implizite Konvertierungen in Folge durchführen um auf ein Wert zu kommen... Sprich wenn du eine Klasse a hast, die im Konstruktor ein Argument der Klasse b verlangt, diese wieder einen Konstruktor für int hat, wird der Aufruf des Konstruktors der Klasse a mit einem int-Wert funktionieren... Was sehr gefährlich, und der eigentliche Grund für die explizite Konstruktordeklaration ist.
Stromberg schrieb:
3. Eigentlich verwendet man copy initializations, ob "implizit" oder "explizit" doch eher selten? Meistens nimmt man doch immer noch den Konstruktor oder? Zumal der doch auch viel schneller ist (direct_initialization).
Auch beim impliziten Konstruktoraufruf wird dennoch der Konstruktor verwendet.
cu André
-
asc schrieb:
...Explizit bei einem Konstruktor heißt, das dieser keine impliziten Umwandlung vornehmen darf. Sprich wenn du einer Klasse nur einem expliziten Konstruktor mit einem int-Wert als Parameter gibst, kannst du diese nicht mit einem short-Wert konstruieren.
Explizite Konstruktoren sind in der Regel den impliziten vorzusehen wenn es um Typsicherheit geht......
Warum geht das hier dann?
#include <iostream> using namespace std; class Foo { public: explicit Foo(int a1_); ~Foo(); private: int m_a1; }; Foo::Foo(int a1_) :m_a1(a1_) { } Foo::~Foo() { } int main() { short x=13; Foo test=Foo(x); return 0; }MfG
Stromberg
-
Weil asc dort etwas falsches geschrieben hat - 'explicit' verhindert nicht die internen Typumwandlungen von C++, es verhindert nur die implizite Umwandlung von int nach foo (in deinem Beispiel), d.h. die Verwendung
foo x=10;oder die Verwendung eines int-Wertes für einen foo.@asc: Und es gibt zwar mitunter mehrere implizite Umwandlungen hintereinander, aber davon maximal eine benutzerdefinierte (Konstruktor oder Umwandlungsoperator).
-
Die explizite Typumwandlung verhindert (so wie CStoll schon schrieb) die automatische Typumwandlung bei benutzerdefineirten Typen.
Sie kommt erst dann wirklich zur Geltung, wenn mehrere Klassen im Spiel sind, z.B.class Foo { explicit Foo(int a); }; class Bar { public: Bar(const Foo &foo); }; int main() { Bar b = 42; // dies führt jetzt zu einem Fehler! }Ohne die Angabe von 'explicit' bei Foo würde die obere Anweisung vom Compiler als
Bar b = Bar(Foo(42)); // bzw. dies ist gleichwertig mit: Bar b(Foo(42))interpretiert.
P.S: Stroustrup gibt in seinem Buch auch noch als Beispiel:
// selbsterstellte Klasse class String { explicit String(int n); // Vorbelegung von n Zeichen }; String s(10); // Initialisierung mit 10 Zeichen String s('a'); // <- Fehler, ohne explicit würden 65 Zeichen vorbelegtEine sogenannte Promotion ist dagegen immer möglich, also von short<->int<->long.
-
Interessanterweise sollte das der Compiler auch ohne explicit abweisen - es ist maximal eine nutzerdefinierte implizite Umwandlung am Stück erlaubt. (Edit: Und so einen Ansatz habe ich schon als Workaround gesehen für Compiler, die nichts mit explicit anfangen können)
Hier mal ein Beispiel, wo es wirklich einen Unterschied macht:
class test { public: explicit test(int); private: ... }; void print(const test& value); ... print(4711); //verboten - Umwandlung ist explizit print(test(4711));//erlaubt ... //und hier der kleine, aber feine Unterschied: test i = 100; //verboten test i(100); //erlaubt
-
Das hier funktioniert bei mir nicht, mit "explicit" oder ohne "explicit":
#include <iostream> using namespace std; class Foo { public: explicit Foo(int a); private: int m_x; }; Foo::Foo(int a) :m_x(a) { } class Bar { public: Bar(const Foo &foo); private: Foo m_y; }; Bar::Bar(const Foo &foo) :m_y(foo) { } int main() { Bar b = 42; // dies führt jetzt zu einem Fehler! }Außerdem wäre für den Compiler "Bar b = 42" u. "Bar b = Foo(42)" immer --> Bar b = Bar(Foo(42));???
Und was ist jetzt eigentlich nochmal die Poente von "explicit"? Was soll damit verhindert bzw. bezweckt werden? Wo liegt der Sinn?
MfG
Stromberg