Ich habe in den letzten vier Jahren viel über diese Frage nachgedacht. Ich bin zu dem Schluss gekommen, dass die meisten Erklärungen über push_back
vs. emplace_back
das ganze Bild verfehlen.
Letztes Jahr hielt ich bei C ++ Now eine Präsentation zum Typabzug in C ++ 14 . Ich spreche um 13:49 Uhr über push_back
vs. emplace_back
, aber es gibt nützliche Informationen, die vorher einige unterstützende Beweise liefern.
Der eigentliche Hauptunterschied hat mit impliziten und expliziten Konstruktoren zu tun. Stellen Sie sich den Fall vor, in dem wir ein einzelnes Argument haben, das wir an push_back
oder übergeben möchten emplace_back
.
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
Nachdem Ihr optimierender Compiler dies in die Hände bekommen hat, gibt es keinen Unterschied zwischen diesen beiden Anweisungen in Bezug auf den generierten Code. Die traditionelle Weisheit ist, dass push_back
ein temporäres Objekt erstellt wird, in das dann verschoben wird, v
während emplace_back
das Argument weitergeleitet und ohne Kopien oder Verschiebungen direkt an Ort und Stelle erstellt wird. Dies mag auf der Grundlage des in Standardbibliotheken geschriebenen Codes zutreffen, es wird jedoch fälschlicherweise davon ausgegangen, dass der optimierende Compiler den von Ihnen geschriebenen Code generieren muss. Die Aufgabe des optimierenden Compilers besteht darin, den Code zu generieren, den Sie geschrieben hätten, wenn Sie ein Experte für plattformspezifische Optimierungen wären und sich nicht um Wartbarkeit, sondern nur um Leistung gekümmert hätten.
Der eigentliche Unterschied zwischen diesen beiden Aussagen besteht darin, dass der Mächtigere emplace_back
jeden Konstruktortyp aufruft, während der Vorsichtigere push_back
nur implizite Konstruktoren aufruft. Implizite Konstruktoren sollen sicher sein. Wenn Sie implizit ein U
aus einem konstruieren können, T
sagen Sie, dass U
alle Informationen T
ohne Verlust gespeichert werden können. Es ist in so ziemlich jeder Situation sicher, eine zu bestehen, T
und niemand wird etwas dagegen haben, wenn Sie es U
stattdessen zu einer machen. Ein gutes Beispiel für einen impliziten Konstruktor ist die Konvertierung von std::uint32_t
nach std::uint64_t
. Ein schlechtes Beispiel für eine implizite Konvertierung ist double
to std::uint8_t
.
Wir wollen bei unserer Programmierung vorsichtig sein. Wir möchten keine leistungsstarken Funktionen verwenden, da es umso einfacher ist, versehentlich etwas Falsches oder Unerwartetes zu tun, je leistungsfähiger die Funktion ist. Wenn Sie explizite Konstruktoren aufrufen möchten, benötigen Sie die Leistung von emplace_back
. Wenn Sie nur implizite Konstruktoren aufrufen möchten, bleiben Sie bei der Sicherheit von push_back
.
Ein Beispiel
std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile
std::unique_ptr<T>
hat einen expliziten Konstruktor von T *
. Da emplace_back
explizite Konstruktoren aufgerufen werden können, wird die Übergabe eines nicht besitzenden Zeigers problemlos kompiliert. Wenn v
der Gültigkeitsbereich jedoch überschritten wird, versucht der Destruktor delete
, diesen Zeiger aufzurufen , der nicht von zugewiesen wurde, new
da es sich nur um ein Stapelobjekt handelt. Dies führt zu undefiniertem Verhalten.
Dies ist nicht nur erfundener Code. Dies war ein echter Produktionsfehler, auf den ich gestoßen bin. Der Code war std::vector<T *>
, aber er besaß den Inhalt. Im Rahmen der Migration zu C ++ 11 habe ich korrekt geändert T *
, std::unique_ptr<T>
um anzuzeigen, dass der Vektor seinen Speicher besitzt. Allerdings war ich im Jahr 2012 diese Änderungen aus meinem Verständnis gründen, in denen ich dachte „emplace_back nicht alles push_back kann tun und mehr, also warum soll ich jemals push_back benutzen?“, So dass ich auch das geändert push_back
zu emplace_back
.
Hätte ich stattdessen den Code als sicherer verwendet push_back
, hätte ich diesen langjährigen Fehler sofort erkannt und er wäre als Erfolg beim Upgrade auf C ++ 11 angesehen worden. Stattdessen habe ich den Fehler maskiert und erst Monate später gefunden.
std::unique_ptr<T>
hat einen expliziten Konstruktor vonT *
. Daemplace_back
explizite Konstruktoren aufgerufen werden können, wird die Übergabe eines nicht besitzenden Zeigers problemlos kompiliert. Wennv
der Gültigkeitsbereich jedoch überschritten wird, versucht der Destruktordelete
, diesen Zeiger aufzurufen , der nicht von zugewiesen wurde,new
da es sich nur um ein Stapelobjekt handelt. Dies führt zu undefiniertem Verhalten.explicit
Konstruktor ist ein Konstruktor, auf den das Schlüsselwortexplicit
angewendet wird. Ein "impliziter" Konstruktor ist ein Konstruktor, der dieses Schlüsselwort nicht hat. Im Fall vonstd::unique_ptr
's Konstruktor vonT *
hat der Implementierer vonstd::unique_ptr
diesen Konstruktor geschrieben, aber das Problem hier ist, dass der Benutzer dieses Typs aufgerufen hatemplace_back
, der diesen expliziten Konstruktor aufgerufen hat. Wenn dies der Fall gewesen wärepush_back
, anstatt diesen Konstruktor aufzurufen, hätte er sich auf eine implizite Konvertierung verlassen, die nur implizite Konstruktoren aufrufen kann.push_back
erlaubt immer die Verwendung einer einheitlichen Initialisierung, die ich sehr mag. Zum Beispiel:Auf der anderen Seite
v.emplace_back({ 42, 121 });
wird nicht funktionieren.quelle
{}
einen tatsächlichen Konstruktor mithilfe der Syntax aufrufen möchten, können Sie die{}
's einfach entfernen und verwendenemplace_back
.{}
aggregate
{}
emplace_back
emplace
Aggregate zu erstellen, ohne explizit einen Boilerplate-Konstruktor zu schreiben . Es ist zu diesem Zeitpunkt auch unklar, ob es als Defekt behandelt wird und somit für ein Backporting in Frage kommt oder ob Benutzer von C ++ <20 SoL bleiben.Abwärtskompatibilität mit Pre-C ++ 11-Compilern.
quelle
push_back
vorzuziehen ist.emplace_back
ist keine "großartige" Version vonpush_back
. Es ist eine potenziell gefährliche Version davon. Lesen Sie die anderen Antworten.Einige Bibliotheksimplementierungen von emplace_back verhalten sich nicht wie im C ++ - Standard angegeben, einschließlich der Version, die mit Visual Studio 2012, 2013 und 2015 geliefert wird.
Um bekannte Compilerfehler zu
std::vector::push_back()
berücksichtigen , verwenden Sie lieber, wenn die Parameter auf Iteratoren oder andere Objekte verweisen, die nach dem Aufruf ungültig werden.Auf einem Compiler enthält v die Werte 123 und 21 anstelle der erwarteten Werte 123 und 123. Dies liegt an der Tatsache, dass der zweite Aufruf von
emplace_back
zu einer Größenänderung führt, bei der der Punktv[0]
ungültig wird.Eine funktionierende Implementierung des obigen Codes würde
push_back()
stattemplace_back()
wie folgt verwendet:Hinweis: Die Verwendung eines Inte-Vektors dient zu Demonstrationszwecken. Ich entdeckte dieses Problem mit einer viel komplexeren Klasse, die dynamisch zugewiesene Mitgliedsvariablen und den Aufruf enthielt,
emplace_back()
zu einem harten Absturz zu führen.quelle
push_back
das in diesem Fall anders sein?