Angenommen, ich habe diese Funktion:
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
Sind diese Aussagen in jeder Gruppierung identisch? Oder gibt es in einigen Initialisierungen eine zusätzliche (möglicherweise optimierbare) Kopie?
Ich habe Leute gesehen, die beide Dinge gesagt haben. Bitte zitieren Sie den Text als Beweis. Fügen Sie bitte auch andere Fälle hinzu.
c++
initialization
rlbond
quelle
quelle
A c1; A c2 = c1; A c3(c1);
.A
, würde die Kopierinitialisierung außerdem das Vorhandensein eines Kopier- / Verschiebungskonstruktors erfordern. Aus diesem Grundstd::atomic<int> a = 1;
ist es in C ++ 17 in Ordnung, aber nicht vorher.Antworten:
C ++ 17 Update
In C ++ 17 wurde die Bedeutung von
A_factory_func()
von der Erstellung eines temporären Objekts (C ++ <= 14) geändert, um lediglich die Initialisierung des Objekts anzugeben, für das dieser Ausdruck in C ++ 17 (lose gesagt) initialisiert wird. Diese Objekte (als "Ergebnisobjekte" bezeichnet) sind die Variablen, die durch eine Deklaration (wiea1
) erstellt wurden, künstliche Objekte, die erstellt wurden, wenn die Initialisierung verworfen wurde, oder wenn ein Objekt für die Referenzbindung benötigt wird (wie zA_factory_func();
. B. in . Im letzten Fall Ein Objekt wird künstlich erstellt, was als "temporäre Materialisierung" bezeichnet wird, daA_factory_func()
es keine Variable oder Referenz gibt, für deren Existenz sonst ein Objekt erforderlich wäre.Als Beispiele in unserem Fall besagen im Fall von
a1
unda2
Sonderregeln, dass in solchen Deklarationen das Ergebnisobjekt eines prvalue-Initialisierers vom gleichen Typ wiea1
variabela1
ist und daherA_factory_func()
das Objekt direkt initialisierta1
. Eine Zwischenbesetzung im funktionalen Stil hätte keine Auswirkung, daA_factory_func(another-prvalue)
nur das Ergebnisobjekt des äußeren Wertes "durchlaufen" wird, um auch das Ergebnisobjekt des inneren Wertes zu sein.Hängt davon ab, welcher Typ
A_factory_func()
zurückgibt. Ich gehe davon aus, dass es ein zurückgibtA
- dann macht es dasselbe - außer dass, wenn der Kopierkonstruktor explizit ist, der erste fehlschlägt. Lesen Sie 8.6 / 14Dies geschieht genauso, da es sich um einen integrierten Typ handelt (dies bedeutet hier keinen Klassentyp). Lesen Sie 8.6 / 14 .
Dies ist nicht dasselbe. Der erste Standard initialisiert, wenn
A
es sich um einen Nicht-POD handelt, und führt keine Initialisierung für einen POD durch (Lesen Sie 8.6 / 9 ). Die zweite Kopie wird initialisiert: Der Wert initialisiert eine temporäre Kopie und kopiert diesen Wert dann inc2
(Lesen Sie 5.2.3 / 2 und 8.6 / 14 ). Dies erfordert natürlich einen nicht expliziten Kopierkonstruktor (Lesen Sie 8.6 / 14 und 12.3.1 / 3 und 13.3.1.3/1 ). Die dritte Methode erstellt eine Funktionsdeklaration für eine Funktionc3
, die eine zurückgibtA
und einen Funktionszeiger auf eine Funktion zurückgibt , die a zurückgibtA
(Read 8.2 ).Eintauchen in Initialisierungen Direkt- und Kopierinitialisierung
Während sie identisch aussehen und dasselbe tun sollen, unterscheiden sich diese beiden Formen in bestimmten Fällen erheblich. Die beiden Formen der Initialisierung sind Direkt- und Kopierinitialisierung:
Es gibt ein Verhalten, das wir jedem von ihnen zuschreiben können:
T
(einschließlichexplicit
derjenigen), und das Argument lautetx
. Die Überlastungsauflösung findet den am besten passenden Konstruktor und führt bei Bedarf alle erforderlichen impliziten Konvertierungen durch.x
in ein Objekt vom Typ zu konvertierenT
. (Es kann dann über dieses Objekt in das zu initialisierende Objekt kopiert werden, sodass auch ein Kopierkonstruktor benötigt wird - dies ist jedoch unten nicht wichtig.)Wie Sie sehen, ist die Kopierinitialisierung in gewisser Weise Teil der direkten Initialisierung im Hinblick auf mögliche implizite Konvertierungen: Während bei der direkten Initialisierung alle Konstruktoren zum Aufrufen verfügbar sind und darüber hinaus jede implizite Konvertierung durchgeführt werden kann, die zum Abgleichen der Argumenttypen erforderlich ist, wird die Kopierinitialisierung durchgeführt kann nur eine implizite Konvertierungssequenz einrichten.
Ich habe mich sehr bemüht und den folgenden Code erhalten, um für jedes dieser Formulare einen anderen Text auszugeben , ohne das "Offensichtliche" durch
explicit
Konstruktoren zu verwenden.Wie funktioniert es und warum gibt es dieses Ergebnis aus?
Direkte Initialisierung
Es weiß zunächst nichts über Konvertierung. Es wird nur versucht, einen Konstruktor aufzurufen. In diesem Fall ist der folgende Konstruktor verfügbar und stimmt genau überein :
Es ist keine Konvertierung erforderlich, geschweige denn eine benutzerdefinierte Konvertierung, die zum Aufrufen dieses Konstruktors erforderlich ist (beachten Sie, dass auch hier keine Konvertierung der Konstantenqualifizierung stattfindet). Die direkte Initialisierung nennt es also.
Initialisierung kopieren
Wie oben erwähnt, erstellt die Kopierinitialisierung eine Konvertierungssequenz, wenn
a
sie nicht typisiertB
oder davon abgeleitet wurde (was hier eindeutig der Fall ist). Es wird also nach Möglichkeiten suchen, die Konvertierung durchzuführen, und die folgenden Kandidaten findenBeachten Sie, wie ich die Konvertierungsfunktion umgeschrieben habe: Der Parametertyp spiegelt den Typ des
this
Zeigers wider , der in einer Nicht-Konstanten-Elementfunktion auf Nicht-Konstante steht. Nun nennen wir diese Kandidatenx
als Argument. Der Gewinner ist die Konvertierungsfunktion: Wenn wir zwei Kandidatenfunktionen haben, die beide einen Verweis auf denselben Typ akzeptieren, gewinnt die Version mit weniger Konstanten (dies ist übrigens auch der Mechanismus, der Nicht-Konstanten-Mitgliedsfunktionen bevorzugt, ruft Nicht auf -const Objekte).Beachten Sie, dass die Konvertierung mehrdeutig ist, wenn wir die Konvertierungsfunktion in eine const-Member-Funktion ändern (da beide einen Parametertyp von
A const&
then haben): Der Comeau-Compiler lehnt sie ordnungsgemäß ab, GCC akzeptiert sie jedoch im nicht pedantischen Modus. Durch Umschalten auf-pedantic
wird jedoch auch die richtige Mehrdeutigkeitswarnung ausgegeben.Ich hoffe, dies hilft etwas, um klarer zu machen, wie sich diese beiden Formen unterscheiden!
quelle
R() == R(*)()
undT[] == T*
. Das heißt, Funktionstypen sind Funktionszeigertypen und Arraytypen sind Zeiger-zu-Element-Typen. Das ist scheiße. Es kannA c3((A()));
umgangen werden (parens um den Ausdruck).Die Zuordnung unterscheidet sich von der Initialisierung .
Die beiden folgenden Zeilen führen die Initialisierung durch . Ein einzelner Konstruktoraufruf wird ausgeführt:
aber es ist nicht gleichbedeutend mit:
Ich habe momentan keinen Text, um dies zu beweisen, aber es ist sehr einfach zu experimentieren:
quelle
double b1 = 0.5;
ist ein impliziter Aufruf des Konstruktors.double b2(0.5);
ist ein expliziter Aufruf.Sehen Sie sich den folgenden Code an, um den Unterschied zu erkennen:
Wenn Ihre Klasse keine expliziten Konstruktoren hat, sind explizite und implizite Aufrufe identisch.
quelle
Erste Gruppierung: Es kommt darauf an, was
A_factory_func
zurückkommt. Die erste Zeile ist ein Beispiel für die Kopierinitialisierung , die zweite Zeile ist die direkte Initialisierung . WennA_factory_func
kehrt einA
Objekt dann sind sie gleichwertig, sie beide Aufruf der Kopierkonstruktor fürA
, andernfalls wird die erste Version erstellt eine rvalue des TypsA
von einem verfügbaren Konvertierungsoperatoren für den RückgabetypA_factory_func
oder entsprechendenA
Konstrukteuren, und ruft dann die Kopie Konstruktor Konstrukta1
aus dieser vorübergehend. Die zweite Version versucht, einen geeigneten Konstruktor zu finden, der alleA_factory_func
Rückgaben akzeptiert oder etwas verwendet, in das der Rückgabewert implizit konvertiert werden kann.Zweite Gruppierung: Es gilt genau die gleiche Logik, außer dass eingebaute Typen keine exotischen Konstruktoren haben und daher in der Praxis identisch sind.
Dritte Gruppierung:
c1
Wird standardmäßig initialisiert undc2
wird von einem temporär initialisierten Wert kopierinitialisiert. Mitgliederc1
mit Pod-Typ (oder Mitglieder von Mitgliedern usw. usw.) können möglicherweise nicht initialisiert werden, wenn der vom Benutzer angegebene Standardkonstruktor (falls vorhanden) diese nicht explizit initialisiert. Dennc2
es hängt davon ab, ob es einen vom Benutzer bereitgestellten Kopierkonstruktor gibt und ob dieser diese Mitglieder entsprechend initialisiert, aber die Mitglieder des temporären werden alle initialisiert (nullinitialisiert, wenn nicht ausdrücklich anders initialisiert). Wie Litb entdeckt,c3
ist eine Falle. Es ist eigentlich eine Funktionsdeklaration.quelle
Bemerkenswert:
[12.2 / 1]
Temporaries of class type are created in various contexts: ... and in some initializations (8.5).
Dh zur Kopierinitialisierung.
[12.8 / 15]
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...
Mit anderen Worten, ein guter Compiler erstellt keine Kopie für die Kopierinitialisierung, wenn dies vermieden werden kann. Stattdessen wird der Konstruktor einfach direkt aufgerufen - dh genau wie bei der Direktinitialisierung.
Mit anderen Worten, die Kopierinitialisierung ist in den meisten Fällen wie die Direktinitialisierung, wenn verständlicher Code geschrieben wurde. Da die Direktinitialisierung möglicherweise willkürliche (und daher wahrscheinlich unbekannte) Konvertierungen verursacht, bevorzuge ich nach Möglichkeit immer die Kopierinitialisierung. (Mit dem Bonus, dass es tatsächlich wie eine Initialisierung aussieht.) </ Opinion>
Technische Güte: [12.2 / 1 Fortsetzung von oben]
Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.
Ich bin froh, dass ich keinen C ++ - Compiler schreibe.
quelle
Sie können den Unterschied zwischen
explicit
undimplicit
Konstruktortypen erkennen, wenn Sie ein Objekt initialisieren:Klassen :
Und in der
main
Funktion:Standardmäßig ist ein Konstruktor so,
implicit
dass Sie zwei Möglichkeiten haben, ihn zu initialisieren:Und indem Sie eine Struktur als
explicit
nur definieren, haben Sie einen Weg als direkt:quelle
Antwort in Bezug auf diesen Teil:
Da die meisten Antworten vor C ++ 11 sind, füge ich hinzu, was C ++ 11 dazu zu sagen hat:
Optimierung oder nicht, sie entsprechen dem Standard. Beachten Sie, dass dies mit den anderen Antworten übereinstimmt. Ich zitiere nur, was der Standard aus Gründen der Korrektheit zu sagen hat.
quelle
Viele dieser Fälle unterliegen der Implementierung eines Objekts, daher ist es schwierig, Ihnen eine konkrete Antwort zu geben.
Betrachten Sie den Fall
In diesem Fall wirkt sich die Implementierung dieser Methoden auf das Verhalten jeder Zeile aus, wenn ein geeigneter Zuweisungsoperator und ein Initialisierungskonstruktor angenommen werden, die ein einzelnes ganzzahliges Argument akzeptieren. Es ist jedoch üblich, dass einer von ihnen den anderen in der Implementierung aufruft, um doppelten Code zu eliminieren (obwohl es in einem so einfachen Fall keinen wirklichen Zweck geben würde.)
Bearbeiten: Wie in anderen Antworten erwähnt, wird in der ersten Zeile tatsächlich der Kopierkonstruktor aufgerufen. Betrachten Sie die Kommentare zum Zuweisungsoperator als Verhalten im Zusammenhang mit einer eigenständigen Zuweisung.
Das heißt, wie der Compiler den Code optimiert, hat dann seine eigenen Auswirkungen. Wenn der initialisierende Konstruktor den Operator "=" aufruft - wenn der Compiler keine Optimierungen vornimmt, würde die oberste Zeile 2 Sprünge ausführen, im Gegensatz zu einem in der untersten Zeile.
In den häufigsten Situationen optimiert Ihr Compiler diese Fälle und beseitigt diese Art von Ineffizienzen. So effektiv werden alle verschiedenen Situationen, die Sie beschreiben, gleich ausfallen. Wenn Sie genau sehen möchten, was gerade getan wird, können Sie sich den Objektcode oder eine Assembly-Ausgabe Ihres Compilers ansehen.
quelle
operator =(const int)
und neinA(const int)
. Weitere Informationen finden Sie in der Antwort von @ jia3ep.Dies ist aus der C ++ - Programmiersprache von Bjarne Stroustrup:
Eine Initialisierung mit einem = wird als Kopierinitialisierung betrachtet . Im Prinzip wird eine Kopie des Initialisierers (des Objekts, von dem wir kopieren) in das initialisierte Objekt eingefügt. Eine solche Kopie kann jedoch wegoptimiert (entfernt) werden, und eine Verschiebungsoperation (basierend auf der Verschiebungssemantik) kann verwendet werden, wenn der Initialisierer ein r-Wert ist. Wenn Sie das = weglassen, wird die Initialisierung explizit. Die explizite Initialisierung wird als direkte Initialisierung bezeichnet .
quelle