Saya baru saja kehilangan tiga hari dalam hidup saya melacak bug yang sangat aneh di mana unordered_map :: insert () menghancurkan variabel yang Anda masukkan. Perilaku yang sangat tidak jelas ini terjadi hanya pada kompiler terbaru: Saya menemukan bahwa clang 3.2-3.4 dan GCC 4.8 adalah satu - satunya kompiler yang mendemonstrasikan "fitur" ini.
Berikut beberapa kode yang dikurangi dari basis kode utama saya yang menunjukkan masalah tersebut:
#include <memory>
#include <unordered_map>
#include <iostream>
int main(void)
{
std::unordered_map<int, std::shared_ptr<int>> map;
auto a(std::make_pair(5, std::make_shared<int>(5)));
std::cout << "a.second is " << a.second.get() << std::endl;
map.insert(a); // Note we are NOT doing insert(std::move(a))
std::cout << "a.second is now " << a.second.get() << std::endl;
return 0;
}
Saya, seperti kebanyakan programmer C ++, mengharapkan output terlihat seperti ini:
a.second is 0x8c14048
a.second is now 0x8c14048
Tetapi dengan dentang 3.2-3.4 dan GCC 4.8 saya mendapatkan ini sebagai gantinya:
a.second is 0xe03088
a.second is now 0
Yang mungkin tidak masuk akal, sampai Anda memeriksa secara dekat dokumen untuk unordered_map :: insert () di http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ di mana overload no 2 adalah:
template <class P> pair<iterator,bool> insert ( P&& val );
Yang merupakan pemindahan referensi universal serakah, memakan apa pun yang tidak cocok dengan kelebihan beban lainnya, dan memindahkan pembuatannya ke dalam sebuah nilai_tipe. Jadi mengapa kode kita di atas memilih overload ini, dan bukan overload unordered_map :: value_type seperti yang mungkin diharapkan sebagian besar?
Jawabannya menatap Anda langsung: unordered_map :: value_type adalah pasangan < const int, std :: shared_ptr> dan kompilator akan berpikir dengan benar bahwa pasangan < int , std :: shared_ptr> tidak dapat dikonversi. Oleh karena itu kompilator memilih overload referensi universal move, dan itu menghancurkan yang asli, meskipun pemrogram tidak menggunakan std :: move () yang merupakan konvensi umum untuk menunjukkan bahwa Anda setuju dengan variabel yang dimusnahkan. Oleh karena itu perilaku merusak penyisipan sebenarnya benar sesuai dengan standar C ++ 11, dan kompiler lama salah .
Anda mungkin dapat melihat sekarang mengapa saya memerlukan tiga hari untuk mendiagnosis bug ini. Itu sama sekali tidak jelas dalam basis kode besar di mana tipe yang dimasukkan ke dalam unordered_map adalah typedef yang didefinisikan jauh dalam istilah kode sumber, dan tidak pernah terpikir oleh siapa pun untuk memeriksa apakah typedef identik dengan value_type.
Jadi pertanyaan saya untuk Stack Overflow:
Mengapa kompiler lama tidak menghancurkan variabel yang dimasukkan seperti kompiler baru? Maksud saya, bahkan GCC 4.7 tidak melakukan ini, dan itu cukup memenuhi standar.
Apakah masalah ini diketahui secara luas, karena tentunya mengupgrade compiler akan menyebabkan kode yang dulu berfungsi tiba-tiba berhenti bekerja?
Apakah komite standar C ++ menginginkan perilaku ini?
Bagaimana Anda menyarankan agar unordered_map :: insert () dimodifikasi untuk memberikan perilaku yang lebih baik? Saya menanyakan hal ini karena jika ada dukungan di sini, saya bermaksud untuk menyampaikan perilaku ini sebagai catatan N ke WG21 dan meminta mereka untuk menerapkan perilaku yang lebih baik.
4.9.0 20131223 (experimental)
masing - masing. Outputnya a.second is now 0x2074088
(atau serupa) untuk saya.
a
tidak. Itu harus membuat salinan. Juga, perilaku ini sepenuhnya bergantung pada stdlib, bukan kompilernya.