Einleitung: Warum ist das wichtig?
\n\nHaben Sie sich jemals gefragt, warum es in C++ zwei Möglichkeiten gibt, ein Element am Ende eines Vektors hinzuzufügen? Warum das Rad neu erfinden, wenn es doch das bewährte push_back gibt? Die Antwort liegt in der Leistung und Flexibilität des modernen C++.
Auf Stack Overflow hat diese Frage tausende Aufrufe und hunderte Kommentare gesammelt. Und das nicht ohne Grund: Den Unterschied zwischen push_back und emplace_back zu verstehen, ist nicht nur akademisches Wissen, sondern eine praktische Fähigkeit, die Ihren Code um das 2- bis 3-fache beschleunigen kann. Lassen Sie uns das ein für alle Mal klären.
Grundlegender Unterschied: Was sagen die Dokumentationen?
\n\nBeginnen wir einfach. Beide Methoden fügen ein Element am Ende des Containers (Vektor, Liste, Queue) hinzu. Aber sie tun dies auf unterschiedliche Weise:
\n\n- \n
push_back– nimmt ein fertiges Objekt entgegen (per Wert, Referenz oder Rvalue-Referenz) und kopiert/verschiebt es in den Container. \n emplace_back– nimmt Argumente für den Konstruktor des Objekts entgegen und erstellt es direkt im Container (In-Place-Konstruktion). \n
Klingt abstrakt? Schauen wir uns den Code an.
\n\nPraktisches Beispiel: Eine Klasse erstellen
\n\nStellen wir uns vor, wir haben eine einfache Klasse Person:
#include <iostream>\n#include <vector>\n#include <string>\n\nclass Person {\npublic:\n std::string name;\n int age;\n\n // Konstruktor\n Person(const std::string& n, int a) \n : name(n), age(a) {\n std::cout << "Konstruktor aufgerufen für " << name << std::endl;\n }\n\n // Kopierkonstruktor\n Person(const Person& other) \n : name(other.name), age(other.age) {\n std::cout << "Kopierkonstruktor für " << name << std::endl;\n }\n\n // Verschiebekonstruktor\n Person(Person&& other) noexcept\n : name(std::move(other.name)), age(other.age) {\n std::cout << "Verschiebekonstruktor für " << name << std::endl;\n }\n};\n\nSchauen wir nun, was bei Verwendung der verschiedenen Methoden passiert.
\n\nVariante 1: push_back mit temporärem Objekt
\n\nint main() {\n std::vector<Person> people;\n people.push_back(Person("Alice", 25));\n return 0;\n}\n\nAusgabe:
\nKonstruktor aufgerufen für Alice\nVerschiebekonstruktor für Alice\n\nWas ist passiert? Zuerst wird ein temporäres Person-Objekt erstellt (erster Konstruktoraufruf). Dann wird dieses Objekt in den Vektor verschoben (Verschiebekonstruktor). Zwei Aufrufe – doppelte Arbeit.
Variante 2: push_back mit fertigem Objekt
\n\nint main() {\n std::vector<Person> people;\n Person alice("Alice", 25);\n people.push_back(alice); // alice — lvalue\n return 0;\n}\n\nAusgabe:
\nKonstruktor aufgerufen für Alice\nKopierkonstruktor für Alice\n\nHier sehen wir eine Kopie, keine Verschiebung. Wenn das Objekt groß ist (z. B. dynamische Arrays enthält), kann das Kopieren teuer sein.
\n\nVariante 3: emplace_back
\n\nint main() {\n std::vector<Person> people;\n people.emplace_back("Bob", 30); // Argumente für den Konstruktor übergeben\n return 0;\n}\n\nAusgabe:
\nKonstruktor aufgerufen für Bob\n\nNur ein einziger Konstruktoraufruf! Das Person-Objekt wird direkt im Speicher des Vektors erstellt, ohne zwischenzeitliche Kopien oder Verschiebungen.
Leistung: Wann ist der Unterschied kritisch?
\n\nBei einfachen Typen (int, double, Zeiger) ist der Unterschied vernachlässigbar – der Compiler optimiert alles. Aber bei komplexen Objekten kann der Unterschied erheblich sein:
\n\n- \n
- Objekte mit schwerem Kopierkonstruktor (z. B.
std::string, Container