1163 Stimmen

Push_back vs emplace_back

Ich bin etwas verwirrt über den Unterschied zwischen push_back und emplace_back.

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

Da es eine Überladung von push_back gibt, die eine Rvalue-Referenz verwendet, sehe ich nicht ganz, welchen Zweck emplace_back erfüllt?

13 Stimmen

19 Stimmen

Beachte, dass (wie Thomas unten sagt), der Code in der Frage stammt aus der Emulation von C++0x in MSVS, nicht was C++0x tatsächlich ist.

5 Stimmen

Ein besseres Papier zum Lesen wäre: open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf. N2642 ist hauptsächlich die Formulierung für den Standard; N2345 ist das Papier, das die Idee erklärt und motiviert.

819voto

Thomas Petit Punkte 12601

Zusätzlich zu dem, was der Besucher gesagt hat:

Die Funktion void emplace_back(Type&& _Val), die von MSCV10 bereitgestellt wird, ist nicht konform und überflüssig, denn wie Sie bemerkt haben, ist sie streng äquivalent zu push_back(Type&& _Val).

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

Anstatt einen value_type zu übernehmen, nimmt sie eine variadische Liste von Argumenten entgegen, das bedeutet, dass Sie die Argumente jetzt perfekt weiterleiten und direkt ein Objekt in einem Container konstruieren können, ohne dass überhaupt ein temporäres Objekt entsteht.

Das ist nützlich, weil egal wie viel Cleverness RVO und Bewegungssemantik mitbringen, es immer noch komplizierte Fälle gibt, in denen ein push_back wahrscheinlich unnötige Kopien (oder Bewegungen) verursacht. Zum Beispiel bei der traditionellen insert()-Funktion einer std::map, müssen Sie ein temporäres Objekt erstellen, das dann in ein std::pair kopiert wird, das dann in die Karte kopiert wird:

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

// drücke die Daumen, dass der Optimierer wirklich gut ist
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString))); 

// sollte für den Optimierer einfacher sein
m.emplace(4, anInt, aDouble, aString);

Warum haben sie also nicht die richtige Version von emplace_back in MSVC implementiert? Eigentlich hat mich das auch vor einer Weile gestört, also habe ich die gleiche Frage im Visual C++ Blog gestellt. Hier ist die Antwort von Stephan T Lavavej, dem offiziellen Betreuer der C++-Standardbibliotheksimplementierung bei Microsoft.

Q: Sind die Beta-2-emplace-Funktionen im Moment nur eine Art Platzhalter?

A: Wie Sie vielleicht wissen, sind variadische Vorlagen nicht in VC10 implementiert. Wir simulieren sie mit Präprozessormaschinerie für Dinge wie make_shared(), tuple und die neuen Dinge in . Diese Präprozessormaschinerie ist relativ schwer zu benutzen und zu pflegen. Außerdem beeinflusst sie die Kompilierung stark, da wir immer wieder Unterheader einfügen müssen. Aufgrund von einer Kombination aus Zeitbeschränkungen und Kompilierungsgeschwindigkeitsbedenken haben wir keine variadischen Vorlagen simuliert in unseren emplace-Funktionen.

Wenn variadische Vorlagen im Compiler implementiert sind, können Sie erwarten, dass wir sie in den Bibliotheken nutzen, auch in unseren emplace-Funktionen. Wir nehmen Konformität sehr ernst, aber leider können wir nicht alles auf einmal erledigen.

Es ist eine verständliche Entscheidung. Jeder, der schon einmal versucht hat, variadische Vorlagen mit abscheulichen Präprozessor-Tricks nachzuahmen, weiß, wie widerlich dieser Kram wird.

157 Stimmen

Diese Klarstellung, dass es sich um ein MSVS10-Problem handelt und nicht um ein C++-Problem, ist hier am wichtigsten. Vielen Dank.

12 Stimmen

Ich glaube, dass Ihre letzte Zeile des C++-Codes nicht funktionieren wird. pair hat keinen Konstruktor, der einen int, einen weiteren int, eine double und als 4. Parameter einen String annimmt. Allerdings können Sie dieses Paar-Objekt direkt mit Hilfe seines teilweisen Konstruktors erstellen. Die Syntax wird natürlich anders sein: m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));

4 Stimmen

Glücklicherweise werden variadische Vorlagen in VS2013 sein, jetzt in der Vorschau.

267voto

visitor Punkte 8156

emplace_back sollte kein Argument vom Typ vector::value_type übernehmen, sondern stattdessen variadische Argumente, die an den Konstruktor des angehängten Elements weitergeleitet werden.

template  void emplace_back(Args&&... args); 

Es ist möglich, einen value_type zu übergeben, der an den Kopierkonstruktor weitergeleitet wird.

Weil es die Argumente weiterleitet, bedeutet dies, dass, wenn Sie keine Rvalue haben, der Container immer noch eine "kopierte" Kopie speichert und keine verschobene Kopie.

 std::vector vec;
 vec.emplace_back(std::string("Hello")); // bewegt
 std::string s;
 vec.emplace_back(s); // kopiert

Aber das obige sollte identisch sein mit dem, was push_back macht. Es ist wahrscheinlich eher für Anwendungsfälle wie:

 std::vector > vec;
 vec.emplace_back(std::string("Hello"), std::string("world")); 
 // sollte diesen Konstruktor aufrufen:
 //template pair(U&& x, V&& y);
 // ohne Kopien der Strings zu machen

0 Stimmen

Du müsstest vec.emplace_back(std::move(s)); verwenden, da s ein lvalue ist, um das Move-Verhalten zu erhalten.

2 Stimmen

@David: aber dann hast du ein verschobenes s im Bereich, ist das nicht gefährlich?

3 Stimmen

Es ist nicht gefährlich, wenn Sie nicht vorhaben, s länger für seinen Wert zu verwenden. Eine Verschiebung macht s nicht ungültig, die Verschiebung wird nur die bereits in s vorgenommene interne Speicherzuweisung stehlen und es in einem Standardzustand (kein String zugeordnet) lassen, der beim Zerstören in Ordnung sein wird, als hätten Sie gerade std::string str; eingegeben.

149voto

vadikrobot Punkte 1585

Optimierung für emplace_back kann anhand des folgenden Beispiels gezeigt werden.

Für emplace_back wird der Konstruktor A (int x_arg) aufgerufen.

Für push_back wird zuerst A (int x_arg) aufgerufen und danach move A (A &&rhs).

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

#include 
#include 
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;
    std::cout << "emplace_back aufrufen:\n";
    a.emplace_back (0);
  }
  {
    std::vector a;
    std::cout << "push_back aufrufen:\n";
    a.push_back (1);
  }
  return 0;
}

Ausgabe:

emplace_back aufrufen:
A (x_arg)

push_back aufrufen:
A (x_arg)
A (A &&)

0 Stimmen

Ich bin hierher gekommen, nachdem ich festgestellt habe, dass mein Code v.emplace_back(x); aufgerufen hat, wobei x explizit verschiebbar, aber nur explizit kopierbar ist. Die Tatsache, dass emplace_back "implizit" explizit ist, lässt mich darüber nachdenken, dass meine Standardfunktion zum Anhängen wahrscheinlich push_back sein sollte. Gedanken?

0 Stimmen

Wenn Sie a.emplace_back zum zweiten Mal aufrufen, wird der Move-Konstruktor aufgerufen!

2 Stimmen

@AndreasK. Das hängt nicht mit emplace_back zusammen, sondern mit der Vergrößerung des Vektors. Sie können das überprüfen, indem Sie drucken, was verschoben wird, anstatt nur "A (A &&)\n" zu drucken, drucken Sie "A (A &&) auf " << rhs.x << "\n". Sie können es in diesem bearbeiteten Code-Schnipsel sehen.

28voto

vaibhav kumar Punkte 687

Spezifischer Anwendungsfall für emplace_back: Wenn Sie ein temporäres Objekt erstellen müssen, das dann in einen Container eingefügt wird, verwenden Sie anstelle von push_back emplace_back. Es erstellt das Objekt direkt im Container.

Notizen:

  1. push_back im obigen Fall erstellt ein temporäres Objekt und verschiebt es in den Container. Allerdings wäre die direkte Konstruktion, die für emplace_back verwendet wird, performanter als das Konstruieren und Verschieben des Objekts (was in der Regel einige Kopiervorgänge mit sich bringt).
  2. Im Allgemeinen können Sie emplace_back anstelle von push_back in allen Fällen ohne größere Probleme verwenden. (Siehe Ausnahmen)

25voto

Noch ein Beispiel für Listen:

// erstellt die Elemente direkt am Platz.
emplace_back("element");

// erstellt ein neues Objekt und kopiert (oder verschiebt) dann dieses Objekt.
push_back(ExplicitDataType{"element"});

5 Stimmen

Bedeutet das, dass emplace_back keine Kopie erstellt? Es speichert nur das tatsächliche Objekt. Wenn wir das Objekt selbst nach emplace_back ändern, sollte sich das Objekt im Vektor auch ändern, oder?

0 Stimmen

@MrNobody mit emplace_back ist es der Container, der die Argumente an den Konstruktor weiterleitet - d. h. weder vor noch nach diesem Aufruf hast du irgendein Objekt zur Hand. Was meinst du mit "das Objekt selbst ändern"? Es gibt NUR das Objekt im Container. Oder du erstellst das Objekt vorher - in diesem Fall verhält es sich genauso wie push_back.

0 Stimmen

@MrNobody Um den von Ihnen erwähnten Effekt zu sehen, muss das Objekt, das in den Vektor eingefügt wird, eine Identität haben, in welchem Fall das Objekt kopiert worden wäre anstatt direkt in den Vektor einzufügen. Siehe das Beispiel des Besuchers.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X