Efisiensi C ++ 11 push_back () dengan std :: move versus emplace_back () untuk objek yang sudah dibangun

88

Dalam C ++ 11 emplace_back()umumnya lebih disukai (dalam hal efisiensi) push_back()daripada karena memungkinkan konstruksi di tempat, tetapi apakah ini masih terjadi saat menggunakan push_back(std::move())dengan objek yang sudah dibangun?

Misalnya, apakah emplace_back()masih disukai dalam kasus seperti berikut ini?

Selain itu, apakah ada manfaat dalam contoh di atas untuk melakukan:

atau apakah perpindahan ke sini sepenuhnya mubazir, atau tidak berpengaruh?

Kerusuhan
sumber
myvector.emplace_back(mystring);menyalin dan tidak bergerak. Dua gerakan lainnya dan harus setara.
TC
Lihat juga pertanyaan dan hasil ini: Survei yang Diminta untuk VC ++ terkait penyisipan dan pengosongan
ildjarn

Jawaban:

117

Mari kita lihat fungsi dari berbagai panggilan yang Anda berikan:

  1. emplace_back(mystring): Ini adalah konstruksi elemen baru di tempat dengan argumen apa pun yang Anda berikan. Karena Anda memberikan nilai l, konstruksi di tempat itu sebenarnya adalah konstruksi salinan, yaitu sama dengan memanggilpush_back(mystring)

  2. push_back(std::move(mystring)): Ini memanggil penyisipan-pindah, yang dalam kasus std::stringini adalah konstruksi-perpindahan di tempat.

  3. emplace_back(std::move(mystring)): Sekali lagi ini adalah konstruksi di tempat dengan argumen yang Anda berikan. Karena argumen itu adalah nilai r, ia memanggil konstruktor-perpindahan std::string, yaitu, konstruksi-perpindahan di tempat seperti pada 2.

Dengan kata lain, jika dipanggil dengan satu argumen tipe T, baik itu nilai r atau nilai l, emplace_backdan push_backekuivalen.

Namun, untuk argumen lainnya, emplace_backmemenangkan perlombaan, misalnya dengan char const*dalam vector<string>:

  1. emplace_back("foo")panggilan std::string(char const*)untuk konstruksi di tempat.

  2. push_back("foo")pertama-tama harus memanggil std::string(char const*)konversi implisit yang diperlukan untuk mencocokkan tanda tangan fungsi, lalu pindah-penyisipan seperti kasus 2. di atas. Oleh karena itu, ini setara denganpush_back(string("foo"))

Arne Mertz
sumber
1
Move constructor umumnya lebih efisien daripada copy constructor, oleh karena itu menggunakan rvalue (case 2, 3) lebih efisien daripada menggunakan lvalue (case 1) meskipun semantiknya sama.
Ryan Li
bagaimana dengan situasi seperti itu? void foo (string && s) {vector.emplace (s); // 1 vector.emplace (std :: move (s)); // 2}
VALOD9
@VALOD itu adalah nomor 1 dan 3 dari daftar saya lagi. sdapat didefinisikan sebagai rujukan nilai r yang mengikat hanya ke rvalues, tetapi di dalam foo, sadalah nilai l.
Arne Mertz
1

Emplace_back mendapatkan daftar referensi nilai r dan mencoba membangun elemen kontainer langsung di tempatnya. Anda dapat memanggil emplace_back dengan semua jenis yang didukung oleh konstruktor elemen kontainer. Ketika memanggil emplace_back untuk parameter yang bukan referensi rvalue, ia 'kembali' ke referensi normal dan setidaknya konstruktor salinan dipanggil ketika parameter dan elemen wadah memiliki tipe yang sama. Dalam kasus Anda 'myvector.emplace_back (mystring)' harus membuat salinan dari string karena kompiler tidak dapat mengetahui bahwa parameter myvector dapat dipindahkan. Jadi masukkan std :: move apa yang memberi Anda keuntungan yang diinginkan. Push_back harus bekerja sebaik emplace_back untuk elemen yang sudah dibangun.

Tunichtgut
sumber
2
Itu adalah ... cara yang menarik untuk mendeskripsikan penerusan / referensi universal.
TC