push_back vs emplace_back

761

Ich bin etwas verwirrt über den Unterschied zwischen push_backund emplace_back.

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);

Da es eine push_backÜberlastung gibt, die eine R-Wert-Referenz nimmt, sehe ich nicht ganz, wozu der Zweck emplace_backwird?

Ronag
quelle
11
Einige gute Lektüre hier: open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2642.pdf
Johan Kotlinski
16
Beachten Sie, dass (wie Thomas weiter unten sagt) der Code in der Frage aus der MSVS- Emulation von C ++ 0x stammt und nicht aus C ++ 0x.
Me22
5
Ein besseres Papier wäre: open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf . N2642 ist meistens eine Formulierung für den Standard; N2345 ist das Papier, das die Idee erklärt und motiviert.
Alan
Beachten Sie, dass es auch in MSVC10 eine template <class _Valty> void emplace_back(_Valty&& _Val)Version gibt, die eine universelle Referenz verwendet, die eine perfekte Weiterleitung an explicitKonstruktoren mit einzelnen Argumenten ermöglicht.
Joki
Verwandte: Gibt es einen Fall, in dem dies push_backvorzuziehen ist emplace_back? Der einzige Fall, an den ich denken kann, ist, wenn eine Klasse irgendwie kopierbar ( T&operator=(constT&)), aber nicht konstruierbar ( T(constT&)) wäre, aber ich kann mir nicht vorstellen, warum man das jemals wollen würde.
Ben

Antworten:

568

Zusätzlich zu dem, was der Besucher sagte:

Die void emplace_back(Type&& _Val)von MSCV10 bereitgestellte Funktion ist nicht konform und redundant, da sie, wie Sie bemerkt haben, streng gleichwertig ist mit push_back(Type&& _Val).

Aber die echte C ++ 0x-Form von emplace_backist wirklich nützlich : void emplace_back(Args&&...);

Anstatt a zu nehmen value_type, wird eine variable Liste von Argumenten verwendet, was bedeutet, dass Sie die Argumente jetzt perfekt weiterleiten und ein Objekt direkt in einen Container ohne temporäre Elemente konstruieren können.

Das ist nützlich, denn egal wie viel Klugheit RVO und Bewegungssemantik auf den Tisch bringen, es gibt immer noch komplizierte Fälle, in denen ein push_back wahrscheinlich unnötige Kopien (oder Verschiebungen) erstellt. Zum Beispiel müssen Sie mit der traditionellen insert()Funktion von a std::mapeine temporäre Funktion erstellen, die dann in a kopiert std::pair<Key, Value>wird und dann in die Karte kopiert wird:

std::map<int, Complicated> m;
int anInt = 4;
double aDouble = 5.0;
std::string aString = "C++";

// cross your finger so that the optimizer is really good
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString))); 

// should be easier for the optimizer
m.emplace(4, anInt, aDouble, aString);

Warum haben sie nicht die richtige Version von emplace_back in MSVC implementiert? Eigentlich hat es mich vor einiger Zeit auch nervt, also habe ich die gleiche Frage im Visual C ++ - Blog gestellt . Hier ist die Antwort von Stephan T Lavavej, dem offiziellen Betreuer der Implementierung der Visual C ++ - Standardbibliothek bei Microsoft.

F: Sind Beta 2 Emplace-Funktionen derzeit nur eine Art Platzhalter?

A: Wie Sie vielleicht wissen, sind in VC10 keine variablen Vorlagen implementiert. Wir simulieren sie mit Präprozessor-Maschinen für Dinge wie make_shared<T>()Tupel und die neuen Dinge in <functional>. Diese Präprozessor-Maschine ist relativ schwierig zu bedienen und zu warten. Dies wirkt sich auch erheblich auf die Kompilierungsgeschwindigkeit aus, da wir wiederholt Unterüberschriften einbeziehen müssen. Aufgrund einer Kombination aus Zeitbeschränkungen und Bedenken hinsichtlich der Kompilierungsgeschwindigkeit haben wir in unseren Emplace-Funktionen keine variablen Vorlagen simuliert.

Wenn verschiedene Vorlagen im Compiler implementiert sind, können Sie davon ausgehen, dass wir sie in den Bibliotheken, einschließlich unserer Emplace-Funktionen, nutzen. Wir nehmen Konformität sehr ernst, aber leider können wir nicht alles auf einmal tun.

Es ist eine verständliche Entscheidung. Jeder, der nur einmal versucht hat, eine variable Vorlage mit schrecklichen Tricks des Präprozessors zu emulieren, weiß, wie ekelhaft dieses Zeug wird.

Thomas Petit
quelle
101
Diese Klarstellung, dass es sich um ein MSVS10-Problem handelt, nicht um ein C ++ - Problem, ist hier der wichtigste Teil. Vielen Dank.
Me22
11
Ich glaube, Ihre letzte Zeile C ++ - Code wird nicht funktionieren. pair<const int,Complicated>hat keinen Konstruktor, der ein int, ein anderes int, ein double und als 4. Parameter einen String nimmt. Sie können dieses Paarobjekt jedoch direkt mit seinem stückweisen Konstruktor erstellen. Die Syntax wird natürlich anders sein:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze
3
Glücklicherweise werden verschiedene Vorlagen in VS2013 angezeigt, jetzt in der Vorschau.
Daniel Earwicker
11
Sollte diese Antwort aktualisiert werden, um die neuen Entwicklungen in vs2013 widerzuspiegeln?
Becko
6
Wenn Sie Visual Studio 2013 oder später verwenden jetzt , sollten Sie Unterstützung für die „echten“ haben , emplace_backso lange es in Visual C ++ implementiert wurde , wenn variadische Vorlagen hinzugefügt wurden: msdn.microsoft.com/en-us/library/hh567368. aspx
kayleeFrye_onDeck
200

emplace_backsollte kein Argument vom Typ vector::value_typeannehmen, sondern verschiedene Argumente, die an den Konstruktor des angehängten Elements weitergeleitet werden.

template <class... Args> void emplace_back(Args&&... args); 

Es ist möglich, eine zu übergeben, value_typedie an den Kopierkonstruktor weitergeleitet wird.

Da die Argumente weitergeleitet werden, bedeutet dies, dass der Container eine "kopierte" Kopie und keine verschobene Kopie speichert, wenn Sie keinen r-Wert haben.

 std::vector<std::string> vec;
 vec.emplace_back(std::string("Hello")); // moves
 std::string s;
 vec.emplace_back(s); //copies

Aber das Obige sollte identisch sein mit dem, was push_backtut. Es ist wahrscheinlich eher für Anwendungsfälle gedacht wie:

 std::vector<std::pair<std::string, std::string> > vec;
 vec.emplace_back(std::string("Hello"), std::string("world")); 
 // should end up invoking this constructor:
 //template<class U, class V> pair(U&& x, V&& y);
 //without making any copies of the strings
Besucher
quelle
2
@ David: Aber dann hast du einen Bereich verschoben s, ist das nicht gefährlich?
Matthieu M.
2
Es ist nicht gefährlich, wenn Sie nicht länger planen, s für seinen Wert zu verwenden. Das Verschieben macht s nicht ungültig, das Verschieben stiehlt nur die interne Speicherzuweisung, die bereits in s vorgenommen wurde, und belässt sie in einem Standardzustand (kein Stich zugewiesen), der bei Zerstörung in Ordnung ist, als hätten Sie gerade std :: string str eingegeben.
David
4
@ David: Ich bin nicht sicher, ob ein verschobenes Objekt für eine andere Verwendung als die anschließende Zerstörung gültig sein muss.
Ben Voigt
46
vec.emplace_back("Hello")funktionieren wird, da das const char*wird Argument werden weitergeleitet an den stringKonstruktor. Das ist der springende Punkt von emplace_back.
Alexandre C.
8
@BenVoigt: Ein verschobenes Objekt muss sich in einem gültigen (aber nicht angegebenen) Zustand befinden. Dies bedeutet jedoch nicht unbedingt, dass Sie eine Operation daran ausführen können. Überlegen Sie std::vector. Ein Leerzeichen std::vectorist ein gültiger Status, den Sie jedoch nicht aufrufen front()können. Dies bedeutet, dass jede Funktion, die keine Vorbedingungen hat, weiterhin aufgerufen werden kann (und Destruktoren können niemals Vorbedingungen haben).
David Stone
96

Die Optimierung für emplace_backkann im nächsten Beispiel demonstriert werden.

Für emplace_backKonstruktor A (int x_arg)wird aufgerufen. Und denn push_back A (int x_arg)heißt zuerst und move A (A &&rhs)wird danach gerufen.

Natürlich muss der Konstruktor als markiert werden explicit, aber für das aktuelle Beispiel ist es gut, explizite Aussagen zu entfernen.

#include <iostream>
#include <vector>
class A
{
public:
  A (int x_arg) : x (x_arg) { std::cout << "A (x_arg)\n"; }
  A () { x = 0; std::cout << "A ()\n"; }
  A (const A &rhs) noexcept { x = rhs.x; std::cout << "A (A &)\n"; }
  A (A &&rhs) noexcept { x = rhs.x; std::cout << "A (A &&)\n"; }

private:
  int x;
};

int main ()
{
  {
    std::vector<A> a;
    std::cout << "call emplace_back:\n";
    a.emplace_back (0);
  }
  {
    std::vector<A> a;
    std::cout << "call push_back:\n";
    a.push_back (1);
  }
  return 0;
}

Ausgabe:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)
Vadikrobot
quelle
21
+1 für das Codebeispiel, das zeigt, was beim Aufrufen von emplace_backvs tatsächlich passiert push_back.
Shawn
Ich kam hierher, nachdem ich bemerkt hatte, dass ich Code hatte, der aufrief, v.emplace_back(x);wobei x explizit verschiebbar, aber nur explizit kopierbar ist. Die Tatsache, dass emplace_back"implizit" explizit ist, lässt mich denken, dass meine Go-to-Funktion zum Anhängen wahrscheinlich sein sollte push_back. Gedanken?
Ben
Wenn Sie das a.emplace_backzweite Mal aufrufen , wird der Verschiebungskonstruktor aufgerufen!
X Æ A-12
8

emplace_backEine konforme Implementierung leitet Argumente an den vector<Object>::value_typeKonstruktor weiter, wenn sie dem Vektor hinzugefügt werden. Ich erinnere mich, dass Visual Studio keine variadic-Vorlagen unterstützt hat, aber mit variadic-Vorlagen in Visual Studio 2013 RC unterstützt wird, wird vermutlich eine konforme Signatur hinzugefügt.

Mit emplace_back, wenn Sie die Argumente direkt weiterleiten vector<Object>::value_typeKonstruktor, nicht wahr eine Art benötigt für bewegliche oder kopierbar seine emplace_backFunktion, genau genommen. In diesem vector<NonCopyableNonMovableObject>Fall ist dies nicht sinnvoll, da vector<Object>::value_type zum Wachsen ein kopierbarer oder beweglicher Typ erforderlich ist.

Aber zur Kenntnis , dass dies für sinnvoll sein könnte std::map<Key, NonCopyableNonMovableObject>, da , wenn Sie einen Eintrag in der Karte zuzuordnen, ist es nicht bewegt werden muß oder überhaupt nicht mehr kopiert werden , im Gegensatz zu vector, was bedeutet , dass Sie verwenden können , std::mapeffektiv mit einem zugeordneten Typ, der weder kopierbar ist noch beweglich.

Germán Diago
quelle
8

Noch eine bei Listen:

// constructs the elements in place.                                                
emplace_back("element");


//It will create new object and then copy(or move) its value of arguments.
push_back(explicitDataType{"element"});
HackSlash
quelle
1

Spezifischer Anwendungsfall für emplace_back: Wenn Sie ein temporäres Objekt erstellen müssen, das dann in einen Container verschoben wird, verwenden Sie emplace_backanstelle vonpush_back . Das Objekt wird direkt im Container erstellt.

Anmerkungen:

  1. push_backIm obigen Fall wird ein temporäres Objekt erstellt und in den Container verschoben. In-Place-Konstruktion verwendet füremplace_back die verwendet wird, ist jedoch leistungsfähiger als das Konstruieren und anschließende Verschieben des Objekts (was im Allgemeinen ein Kopieren erfordert).
  2. Im Allgemeinen können Sie emplace_backstatt push_backin allen Fällen ohne große Probleme verwenden. (Siehe Ausnahmen )
Vaibhav Kumar
quelle