push_back vs emplace_back

762

Saya agak bingung tentang perbedaan antara push_backdan emplace_back.

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

Karena ada push_backkelebihan mengambil referensi nilai saya tidak cukup melihat apa tujuan emplace_backmenjadi?

ronag
sumber
11
Beberapa bacaan yang bagus di sini: open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2642.pdf
Johan Kotlinski
16
Perhatikan bahwa (seperti yang dikatakan Thomas di bawah), kode dalam pertanyaan adalah dari emulasi C ++ 0x MSVS , bukan apa sebenarnya C ++ 0x.
saya22
5
Makalah yang lebih baik untuk dibaca adalah: open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf . N2642 sebagian besar kata-kata untuk Standar; N2345 adalah makalah yang menjelaskan dan memotivasi ide.
Alan
Perhatikan bahwa bahkan di MSVC10 ada template <class _Valty> void emplace_back(_Valty&& _Val)versi yang mengambil referensi universal yang menyediakan penerusan yang sempurna untuk explicitkonstruktor argumen tunggal.
joki
Terkait: Apakah ada kasus push_backyang lebih disukai emplace_back? Satu-satunya kasus yang dapat saya pikirkan adalah jika suatu kelas entah bagaimana dapat disalin ( T&operator=(constT&)) tetapi tidak dapat dikonstruksikan ( T(constT&)), tetapi saya tidak dapat memikirkan mengapa orang menginginkannya.
Ben

Jawaban:

569

Selain apa yang dikatakan pengunjung:

Fungsi yang void emplace_back(Type&& _Val)disediakan oleh MSCV10 tidak sesuai dan mubazir, karena seperti yang Anda catat, ini setara dengan push_back(Type&& _Val).

Tapi bentuk nyata C ++ 0x emplace_backsangat berguna void emplace_back(Args&&...):;

Alih-alih mengambil value_typeitu membutuhkan daftar argumen variadik, sehingga itu berarti bahwa Anda sekarang dapat dengan sempurna meneruskan argumen dan membangun langsung objek ke wadah tanpa sementara sama sekali.

Itu berguna karena tidak peduli seberapa banyak kepintaran RVO dan memindahkan semantik ke meja, masih ada kasus rumit di mana push_back cenderung membuat salinan yang tidak perlu (atau memindahkan). Misalnya, dengan insert()fungsi tradisional a std::map, Anda harus membuat sementara, yang kemudian akan disalin ke std::pair<Key, Value>, yang kemudian akan disalin ke peta:

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);

Jadi mengapa mereka tidak mengimplementasikan versi emplace_back yang tepat di MSVC? Sebenarnya, itu menyadap saya beberapa waktu yang lalu, jadi saya mengajukan pertanyaan yang sama di blog Visual C ++ . Berikut adalah jawaban dari Stephan T Lavavej, pengelola resmi implementasi perpustakaan standar Visual C ++ di Microsoft.

T: Apakah fungsi beta 2 emplace hanya semacam placeholder sekarang?

A: Seperti yang Anda ketahui, template variadic tidak diimplementasikan dalam VC10. Kami mensimulasikan mereka dengan mesin preprocessor untuk hal-hal seperti make_shared<T>(), tuple, dan hal-hal baru di <functional>. Mesin preprosesor ini relatif sulit digunakan dan dirawat. Selain itu, ini secara signifikan memengaruhi kecepatan kompilasi, karena kami harus berulang kali menyertakan subpos. Karena kombinasi dari batasan waktu dan masalah kecepatan kompilasi kami, kami belum mensimulasikan templat variadic di fungsi emplace kami.

Ketika templat variadic diimplementasikan dalam kompiler, Anda dapat berharap bahwa kami akan memanfaatkannya di perpustakaan, termasuk di fungsi emplace kami. Kami menanggapi kesesuaian dengan sangat serius, tetapi sayangnya, kami tidak dapat melakukan semuanya sekaligus.

Itu keputusan yang bisa dimengerti. Setiap orang yang mencoba sekali saja untuk meniru template variadic dengan trik mengerikan preprocessor tahu betapa menjijikkannya hal ini.

Thomas Petit
sumber
101
Klarifikasi bahwa itu adalah masalah MSVS10, bukan masalah C ++ adalah bagian terpenting di sini. Terima kasih.
me22
11
Saya yakin baris terakhir kode C ++ Anda tidak akan berfungsi. pair<const int,Complicated>tidak memiliki konstruktor yang mengambil int, int lain, parameter ganda dan sebagai string ke-4. Namun, Anda bisa langsung membangun objek pasangan ini menggunakan konstruktor piecewise-nya. Sintaksnya akan berbeda, tentu saja:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze
3
Template variad yang bahagia akan ada di VS2013, sekarang dalam pratinjau.
Daniel Earwicker
11
haruskah jawaban ini diperbarui untuk mencerminkan perkembangan baru di vs2013?
becko
6
Jika Anda menggunakan Visual Studio 2013 atau lebih baru sekarang , Anda harus memiliki dukungan untuk "nyata" emplace_backselama itu diterapkan ke dalam Visual C ++ ketika templat variadic ditambahkan: msdn.microsoft.com/en-us/library/hh567368. aspx
kayleeFrye_onDeck
200

emplace_backtidak boleh mengambil argumen tipe vector::value_type, melainkan argumen variadik yang diteruskan ke konstruktor item terlampir.

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

Dimungkinkan untuk meneruskan value_typeyang akan diteruskan ke copy constructor.

Karena ini meneruskan argumen, ini berarti bahwa jika Anda tidak memiliki nilai, ini masih berarti bahwa wadah akan menyimpan salinan yang "disalin", bukan salinan yang dipindahkan.

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

Tetapi hal di atas harus identik dengan apa yang push_backdilakukannya. Ini mungkin lebih dimaksudkan untuk kasus penggunaan seperti:

 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
pengunjung
sumber
2
@ David: tetapi kemudian Anda memiliki ruang slingkup bergerak , bukankah itu berbahaya?
Matthieu M.
2
Tidak berbahaya jika Anda tidak berencana untuk menggunakan nilainya lagi. Bergerak tidak membuat s tidak valid, langkah tersebut hanya akan mencuri alokasi memori internal yang sudah dilakukan dalam s dan membiarkannya dalam keadaan default (tidak ada sengatan dialokasikan) yang bila dihancurkan akan baik-baik saja seolah-olah Anda baru saja mengetik std :: string str;
David
4
@ David: Saya tidak yakin bahwa objek yang dipindahkan-dari harus valid untuk penggunaan apa pun kecuali kerusakan selanjutnya.
Ben Voigt
46
vec.emplace_back("Hello")akan berfungsi, karena const char*argumen akan diteruskan ke stringkonstruktor. Inilah inti dari emplace_back.
Alexandre C.
8
@BenVoigt: Objek yang dipindahkan dari diperlukan harus dalam keadaan valid (tapi tidak ditentukan). Ini tidak berarti Anda dapat melakukan operasi apa pun di atasnya. Pertimbangkan std::vector. Kosong std::vectoradalah keadaan yang valid, tetapi Anda tidak dapat memanggilnya front(). Ini berarti bahwa setiap fungsi yang tidak memiliki prasyarat masih dapat dipanggil (dan destruktor tidak akan pernah memiliki prasyarat).
David Stone
96

Optimasi untuk emplace_backdapat ditunjukkan dalam contoh berikut.

Untuk emplace_backkonstruktor A (int x_arg)akan dipanggil. Dan untuk push_back A (int x_arg)disebut pertama dan move A (A &&rhs)sesudahnya disebut.

Tentu saja, konstruktor harus ditandai sebagai explicit, tetapi untuk contoh saat ini baik untuk menghilangkan kesaksian.

#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;
}

keluaran:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)
vadikrobot
sumber
21
1 untuk contoh kode yang menunjukkan apa yang sebenarnya terjadi saat memanggil emplace_backvs push_back.
Shawn
Saya datang ke sini setelah memperhatikan bahwa saya memiliki kode yang memanggil v.emplace_back(x);x yang secara eksplisit move-constructable tetapi hanya secara eksplisit copy-constructable. Fakta yang emplace_back"tersirat" eksplisit membuat saya berpikir bahwa fungsi masuk saya untuk menambahkan mungkin seharusnya push_back. Pikiran?
Ben
Jika Anda memanggil a.emplace_backkedua kalinya, konstruktor pemindahan akan dipanggil!
X Æ A-12
8

emplace_backimplementasi yang sesuai akan meneruskan argumen ke vector<Object>::value_typekonstruktor ketika ditambahkan ke vektor. Saya ingat Visual Studio tidak mendukung templat variadic, tetapi dengan templat variadic akan didukung di Visual Studio 2013 RC, jadi saya kira tanda tangan yang sesuai akan ditambahkan.

Dengan emplace_back, jika Anda meneruskan argumen secara langsung ke vector<Object>::value_typekonstruktor, Anda tidak perlu tipe yang dapat dipindahkan atau disalin untuk emplace_backfungsi, secara tegas. Dalam vector<NonCopyableNonMovableObject>hal ini, ini tidak berguna, karena vector<Object>::value_type perlu jenis yang dapat disalin atau bergerak untuk tumbuh.

Tetapi perhatikan bahwa ini bisa berguna untuk std::map<Key, NonCopyableNonMovableObject>, karena begitu Anda mengalokasikan entri di peta, itu tidak perlu dipindahkan atau disalin lagi, tidak seperti dengan vector, yang berarti bahwa Anda dapat menggunakan std::mapsecara efektif dengan tipe yang dipetakan yang tidak dapat disalin maupun bergerak.

Germán Diago
sumber
8

Satu lagi dalam hal daftar:

// 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
sumber
1

Kasus penggunaan khusus untuk emplace_back: Jika Anda perlu membuat objek sementara yang kemudian akan didorong ke dalam wadah, gunakan emplace_backsebagai ganti push_back. Ini akan membuat objek di tempat di dalam wadah.

Catatan:

  1. push_backdalam kasus di atas akan membuat objek sementara dan memindahkannya ke wadah. Namun, konstruksi di tempat yang digunakan emplace_backakan lebih berkinerja daripada membangun dan kemudian memindahkan objek (yang umumnya melibatkan penyalinan).
  2. Secara umum, Anda dapat menggunakan emplace_backalih-alih push_backdalam semua kasus tanpa banyak masalah. (Lihat pengecualian )
vaibhav kumar
sumber