Saya telah memikirkan pertanyaan ini sedikit selama empat tahun terakhir. Saya sampai pada kesimpulan bahwa sebagian besar penjelasan tentang push_backvs. emplace_backmelewatkan gambaran lengkap.
Tahun lalu, saya memberikan presentasi di C ++ Now on Type Deduction in C ++ 14 . Saya mulai berbicara tentang push_backvs. emplace_backpada 13:49, tetapi ada informasi berguna yang menyediakan beberapa bukti pendukung sebelum itu.
Perbedaan utama sebenarnya berkaitan dengan konstruktor implisit vs eksplisit. Pertimbangkan kasus di mana kita memiliki satu argumen yang ingin kita sampaikan push_backatau emplace_back.
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
Setelah kompiler pengoptimalisasi Anda berhasil, tidak ada perbedaan antara kedua pernyataan ini dalam hal kode yang dihasilkan. Kearifan tradisional adalah bahwa push_backakan membangun objek sementara, yang kemudian akan pindah ke vsedangkan emplace_backakan meneruskan argumen bersama dan membangunnya langsung di tempat tanpa salinan atau bergerak. Ini mungkin benar berdasarkan kode seperti yang ditulis di perpustakaan standar, tetapi itu membuat asumsi yang salah bahwa tugas kompiler mengoptimalkan adalah untuk menghasilkan kode yang Anda tulis. Pekerjaan kompiler pengoptimal sebenarnya untuk menghasilkan kode yang Anda tulis jika Anda seorang ahli optimasi platform spesifik dan tidak peduli tentang rawatan, hanya kinerja.
Perbedaan aktual antara kedua pernyataan ini adalah bahwa semakin kuat emplace_backakan memanggil semua jenis konstruktor di luar sana, sedangkan yang lebih berhati push_back- hati akan memanggil hanya konstruktor yang implisit. Konstruktor implisit seharusnya aman. Jika Anda dapat secara implisit membangun Udari T, Anda mengatakan bahwa Udapat menyimpan semua informasi Ttanpa kehilangan. Aman dalam hampir semua situasi untuk dilewati Tdan tidak ada yang keberatan jika Anda menjadikannya sebagai Ugantinya. Contoh bagus konstruktor implisit adalah konversi dari std::uint32_tke std::uint64_t. Contoh buruk konversi implisit adalah doubleuntuk std::uint8_t.
Kami ingin berhati-hati dalam pemrograman kami. Kami tidak ingin menggunakan fitur yang kuat karena semakin kuat fitur, semakin mudah untuk melakukan sesuatu yang tidak benar atau tidak terduga. Jika Anda bermaksud memanggil konstruktor eksplisit, maka Anda memerlukan kekuatan emplace_back. Jika Anda ingin memanggil konstruktor implisit saja, tetap dengan keamanan push_back.
Sebuah contoh
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>memiliki konstruktor eksplisit dari T *. Karena emplace_backdapat memanggil konstruktor eksplisit, melewatkan pointer yang tidak memiliki mengkompilasi dengan baik. Namun, ketika vkeluar dari ruang lingkup, destruktor akan berusaha memanggil deletepointer itu, yang tidak dialokasikan oleh newkarena itu hanya objek tumpukan. Ini mengarah pada perilaku yang tidak terdefinisi.
Ini bukan hanya kode yang ditemukan. Ini adalah bug produksi nyata yang saya temui. Kode itu std::vector<T *>, tetapi memiliki isinya. Sebagai bagian dari migrasi ke C ++ 11, saya benar berubah T *untuk std::unique_ptr<T>menunjukkan bahwa vektor dimiliki memori. Namun, saya mendasarkan perubahan ini dari pemahaman saya pada tahun 2012, di mana saya berpikir "emplace_back melakukan semua yang bisa dilakukan push_back dan banyak lagi, jadi mengapa saya menggunakan push_back?", Jadi saya juga mengubah push_backke emplace_back.
Seandainya saya meninggalkan kode sebagai menggunakan yang lebih aman push_back, saya akan langsung menangkap bug lama ini dan itu akan dilihat sebagai keberhasilan meningkatkan ke C ++ 11. Sebagai gantinya, saya menutupi bug dan tidak menemukannya sampai berbulan-bulan kemudian.