Arten der Initialisierung von Objekten
-
Hi,
es gibt ja zwei Arten ein Objekt zu initialisieren, welche verwendet ihr?
Objekt mein_objekt = Objekt(a, b); // so ? Objekt mein_objekt(a, b); // oder so?
G hibbes
-
Ich verwende die zweite Variante, da ich die erste Variante noch nie angetroffen habe. (Ausserdem ist es weniger Schreibarbeit :D)
-
Ich nehme immer die 2. Variante, denn bei der 1. wird ein temporäres Objekt erzeugt und kopiert (auch wenn ich weiß, dass der Compiler die Kopie in der Regel wegoptimiert).
Einzige Ausnahme: Wenn der Konstruktor nur einen Parameter hat, schreibe ich stattObject o(wert);
lieberObject o = wert;
, dabei ist beides äquivalent und diese Schreibweise finde ich schöner.Edit: das geht natürlich nur, wenn der Konstruktor nicht
explicit
ist.
-
ipsec schrieb:
Edit: das geht natürlich nur, wenn der Konstruktor nicht
explicit
ist,und der Copy Konstruktor
public
ist,
da ja eigentlich zuerst ein temporäres Objekt erstellt wird, welches dann dem Copy Konstruktor übergeben wird.
-
Dweb schrieb:
ipsec schrieb:
Edit: das geht natürlich nur, wenn der Konstruktor nicht
explicit
ist,und der Copy Konstruktor
public
ist,
da ja eigentlich zuerst ein temporäres Objekt erstellt wird, welches dann dem Copy Konstruktor übergeben wird.Nein.
Object o = wert;
ist syntaktisch äquivalent mitObject o(wert);
, wenn der Konstruktor wie erwähnt nicht explicit ist.
Beispiel:class foo { private: foo(const foo&); public: foo(int i) {}; }; // ... foo f1(0); // kein Fehler foo f2 = 0; // auch kein Fehler
Funktioniert zumindest bei mir (VS 2008).
Dern genauen Standard-Wortlauf kenne ich aber nicht.
-
ipsec schrieb:
Dweb schrieb:
ipsec schrieb:
Edit: das geht natürlich nur, wenn der Konstruktor nicht
explicit
ist,und der Copy Konstruktor
public
ist,
da ja eigentlich zuerst ein temporäres Objekt erstellt wird, welches dann dem Copy Konstruktor übergeben wird.Nein.
Object o = wert;
ist syntaktisch äquivalent mitObject o(wert);
, wenn der Konstruktor wie erwähnt nicht explicit ist.
Beispiel:class foo { private: foo(const foo&); public: foo(int i) {}; }; // ... foo f1(0); // kein Fehler foo f2 = 0; // auch kein Fehler
Funktioniert zumindest bei mir (VS 2008).
Dern genauen Standard-Wortlauf kenne ich aber nicht.GCC nimmts nicht mit der Fehklermeldung, daß der foo(const foo&) private sei.
-
hibbes schrieb:
Objekt mein_objekt = Objekt(a, b); // so ? Objekt mein_objekt(a, b); // oder so?
Ich verwende beides je nach Situation. Das erste nennt sich "Kopierinitialisierung". Das zweite nennt sich "Direktinitialisierung". Das erste erfordert einen öffentlichen impliziten Kopierkonstruktor und ggf einen impliziten Konvertierungskonstruktor (wenn der Typ der rechten Seite ein anderer ist wie zB bei
complex<double> z = 1;
).
Bei einem guten Compiler sollte das aber keine Unterschiede machen. Also, angenommen, dass beide Versionen in einem bestimmten Fall erlaubt sind, dann wird ein guter Compiler bei der ersten Version gegenüber der zweiten keine zusätzlichen temporären Objekte anlegen. Der C++ Standard erlaubt explizit das Wegoptimieren von "unnötigen" Kopien in bestimmten Kontexten, siehe §12.8.
kk
-
Also gibt es doch einen Unterschied? Ich habe mal bei Google-Books im Stroustrup nachgeschaut und dort war folgendes Beispiel:
Datum datum = Datum(23, 6, 1983); Datum heiligabend(24, 12, 1990); // abgekürzte Schreibweise
Meint er mit abgekürzter Schreibweise nun dass die beiden Initialisierungen wirklich 100% identisch sind? Müsste ja so sein, er ist ja Wissenschaftler. Das stünde ja dann im Widerspruch zu der Aussage die krümelkacker getroffen hat, bezüglich eines Unterschiedes.
G hibbes
-
Bei der Initialisierung wird kein temporäres Objekt erstellt. Dies geschieht nur bei einer Zuweisung.
-
hibbes schrieb:
Also gibt es doch einen Unterschied? Ich habe mal bei Google-Books im Stroustrup nachgeschaut und dort war folgendes Beispiel:
Datum datum = Datum(23, 6, 1983); Datum heiligabend(24, 12, 1990); // abgekürzte Schreibweise
Meint er mit abgekürzter Schreibweise nun dass die beiden Initialisierungen wirklich 100% identisch sind? Müsste ja so sein, er ist ja Wissenschaftler. Das stünde ja dann im Widerspruch zu der Aussage die krümelkacker getroffen hat, bezüglich eines Unterschiedes.
G hibbes
Ist
a+=b;
eine abkürzende Schreibweise für
a=a+b;
?
-
So wie ich es bis jetzt verstanden habe: Ja
-
hibbes schrieb:
So wie ich es bis jetzt verstanden habe: Ja
Geht mir auch so.
Aber bei a=a+b; wird im Gegensatz zu a+=b; ein neues Objekt erzeugt, dann dem a mit dem operator= zugewiesen und dann zerstört.
Das tut also was total anderes, und trotzdem würde auch ein Wissenschaftler von einer abkürzenden Schreibweise sprechen.
-
Woher habt ihr eigentlich die Informationen wie der Compiler dann damit umgeht? Es muss ja irgendwo wie bei den RFCs vorgeschrieben sein wie so etwas implementiert werden muss.
Wenn das mit den temporären Objekten halt von der Optimierung oder auch von der Implementierung des Compilers abhängt, dann wäre es ja für die Programmierung an sich im Normalfall irrelevant.
Gibt es eine Möglichkeit selbst diese temporären Objekte sichtbar zu machen, um eigene Tests zu entwickeln?
-
ipsec schrieb:
Dweb schrieb:
ipsec schrieb:
Edit: das geht natürlich nur, wenn der Konstruktor nicht
explicit
ist,und der Copy Konstruktor
public
ist,
da ja eigentlich zuerst ein temporäres Objekt erstellt wird, welches dann dem Copy Konstruktor übergeben wird.Nein.
Object o = wert;
ist syntaktisch äquivalent mitObject o(wert);
, wenn der Konstruktor wie erwähnt nicht explicit ist.Nein.
Object o = wert
ist nur dann das selbe wieObject o(wert);
, wenn wert bereits vom Typ Object ist. Andernfalls ist es das Selbe wieObject o = Object(wert);
was das Selbe ist wieObject o(Object(wert));
Das liegt daran, dass die Initialisierung durch Gleichheitszeichen eigentlich nur mit Objekten des selben Typs möglich ist und den Copy-Konstruktor aufruft. Wird ein Objekt eines anderen Typs übergeben, dann wird es, um diesen Anforderungen zu genügen, erst in ein temporäres Objekt vom richtigen Typ umgewandelt (durch Umwandlungs-Konstruktor oder Typumwandlungsoperator) und danach dann mittels Copy-Ctor die Initialisierung durch Gleichheitszeichen durchgeführt.
Der Copy-Ctor darf zwar wegoptimiert werden, es muss aber trotzdem der Copy-Ctor zugreifbar sein.volkard schrieb:
hibbes schrieb:
So wie ich es bis jetzt verstanden habe: Ja
Geht mir auch so.
Aber bei a=a+b; wird im Gegensatz zu a+=b; ein neues Objekt erzeugt, dann dem a mit dem operator= zugewiesen und dann zerstört.
Das tut also was total anderes, und trotzdem würde auch ein Wissenschaftler von einer abkürzenden Schreibweise sprechen.Dazu kommt, dass der Ausdruck a bei
a=a+b
zweimal ausgewertet wird, was relevant ist wenn diese Auswertung Nebeneffekte hat. Wenn ein Wissenschaftler hier von abkürzender Schreibweise spricht wird er aber auch sofort im Nachsatz dazusagen dass es diese beiden Einschränkungen/Abweichungen (temporäres Objekt und doppelte Auswertung) gibt. Alles andere wäre nicht wissenschaftlich korrektIch würd mich daher eher davor hüten das als abkürzende Schreibweise zu bezeichnen, weils das eben nicht ist und solche Nachsätze dann doch viel zu gerne vergessen werden.
-
pumuckl schrieb:
Einschränkungen/Abweichungen (temporäres Objekt und doppelte Auswertung) gibt. Alles andere wäre nicht wissenschaftlich korrekt
Hängt von der Ebene ab, in der man erzählt. Inhaltlich ist das bei eingebauten Typen die akü S und sollte es bei anderen Typen eben auch sein.
-
Wenn es von der Ebene der Programmierung und nicht von der Ebene der Übersetzung egal ist wie ich initialisiere dann kann es als gleich(andere Schreibweise) angesehen werden. Wenn nicht hat Stroustrup hier ungenau beschrieben, was ich mir aber auch nicht so vorstellen mag.
-
hibbes schrieb:
Woher habt ihr eigentlich die Informationen wie der Compiler dann damit umgeht?
Der C++ ISO Standard steckt mit seinen Regeln das ab, was der Compiler machen darf und was er nicht machen darf. Ob/wie ein Compiler die Freiheiten bzgl "Wegoptimieren von unnötigen Kopien" ausnutzt, kann überprüft werden. Beispiel:
#include <iostream> struct ding { ding() {} ding(ding const&) {std::cout<<'C';} }; ding quelle() { ding d; return d; } int main() { std::cout << '['; ding x = quelle(); std::cout << "]\n"; }
Bei mir bekomme ich die Ausgabe []. Es werden also keine unnötigen Kopien gemacht. Beim codepad.org-Compiler (vermutlich eine ältere Version des GCCs) bekomme ich die Ausgabe [CC]. Es werden also zwei Kopien erzeugt:
- d --> Funktionsrückgabewert
- Funktionsrückgabewert --> x
Nach §12.8 des C++ ISO Standards dürfen diese drei Objekte in diesem Fall zu einem einzigen zusammen gefasst werden. Damit erübrigt sich das Kopieren. Die erste Optimierung in diesem Fall nennt sich "named return value optimization". Hier werden die Objekte d und der Rückgabewert einfach zusammengelegt. Die zweite hat soweit ich weiß keinen besonderen Namen. Man fasst beides unter "copy elision" zusammen. In der Zeile "ding x = ..." wird ein Objekt mit einem temporären Objekt desselben Typs initialisiert. Hier erlaubt §12.8 wieder ein Verschmelzen der beiden Objekte zu einem einzigen Objekt.
Compiler, die diese Optimierungen durchführen machen daraus in etwa folgenden Pseudo-Code:
void quelle(ding* rueckgabe) { ding-Objekt bei *rueckgabe konstruieren } int main() { std::cout << '['; noinit ding x; // <-- noch keine Initialisierung quelle(&x); // <-- x ist nach dem Aufruf gültig std::cout << "]\n"; }
kk
-
Danke für die ausführliche Erklärung,
da die Compiler hier ja dürfen und nix müssen werde ich die Initialisierungen dann erst einmal als gleich, im Sinne der Sprache, ansehen. Aber ich werde auch im Hinterkopf behalten dass es "unter der Haube" nicht gleich behandelt werden muss.
-
Die relevanten Passagen sind die hier (aus dem Entwurf N1804.pdf):
§8.5/11:
The form of initialization (using parentheses oris generally insignificant, but does matter when the entity being initialized has a class type...
§8.5/12:
The initialization that occurs in argument passing, function return, throwing an exception, handling an exception, and brace-enclosed initializer lists is called copy-initialization and is equivalent to the form
T x = a;
§8.5/13:
The initialization that occurs in new expressions, static_cast expressions, functional notation type conversions, and base and member initializers is called direct-initialization and is equivalent to the form
T x(a);
§8.5/15:
The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. The source type is not defined when the initializer is brace-enclosed or when it is a parenthesized list of expressions.-
...
-
If the destination type is a (possibly cv qualified) class type
-
...
-
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated, and the best one is chosen through overload resolution. The constructor so selected is called to initialize the object, with the initializer expression(s) as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
-
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution. If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is an rvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the
intermediate result directly into the object being initialized; see 12.2, 12.8. -
...
Aus §8.5/15 geht allerdings nicht hervor, dass bei der Kopierinitialisierungssyntax zum Initialisieren des Zielobjektes keine "expliziten" Konstruktoren in Frage kommen. Das steht dann hier:
§12.3.1/2:
An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used.Das bedeutet also, dass
Klasse x = Klasse(1,2,3);
nur dann funktioniert, falls der copy-ctor public und nicht explizit ist. Hier wird logisch gesehen ein temporäres Objekt erzeugt und dann kopiert. Das Kopieren kann wegoptimiert werden (siehe §12.8). Ein copy-ctor muss trotzdem verfügbar sein, auch wenn er nicht benutzt wird. Wir haben nur im Fall
Klasse x (1,2,3);
die Garantie, dass kein unnötiges temporäres Objekt entsteht.
Falls das Quellobjekt kein temporäres ist wie hier:
Klasse x = ...; Klasse y = x; // #1 Klasse z (x); // #2
passiert bei #1 und #2 genau dasselbe, sofern #1 und #2 legal sind. #1 wird aber nicht funktionieren, wenn der copy-ctor privat und/oder explizit ist.
kk
-