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.