ฉันเพิ่งเสียชีวิตไปสามวันในการติดตามข้อผิดพลาดที่แปลกมากที่ unordered_map :: insert () ทำลายตัวแปรที่คุณแทรก ลักษณะการทำงานที่ไม่ชัดเจนนี้เกิดขึ้นในคอมไพเลอร์ล่าสุดเท่านั้น: ฉันพบว่า clang 3.2-3.4 และ GCC 4.8 เป็นคอมไพเลอร์เพียงตัวเดียวที่แสดง "คุณลักษณะ" นี้
นี่คือโค้ดที่ลดลงบางส่วนจากฐานรหัสหลักของฉันซึ่งแสดงให้เห็นถึงปัญหา:
#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;
}
ฉันเช่นเดียวกับโปรแกรมเมอร์ C ++ ส่วนใหญ่คาดว่าผลลัพธ์จะมีลักษณะดังนี้:
a.second is 0x8c14048
a.second is now 0x8c14048
แต่ด้วยเสียงดังกราว 3.2-3.4 และ GCC 4.8 ฉันได้รับสิ่งนี้แทน:
a.second is 0xe03088
a.second is now 0
ซึ่งอาจไม่สมเหตุสมผลจนกว่าคุณจะตรวจสอบเอกสารสำหรับ unordered_map :: insert () อย่างละเอียดที่http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/โดยที่ overload no 2 คือ:
template <class P> pair<iterator,bool> insert ( P&& val );
ซึ่งเป็นการย้ายข้อมูลอ้างอิงสากลแบบโลภมากเกินไปใช้สิ่งใดก็ตามที่ไม่ตรงกับโอเวอร์โหลดอื่น ๆ และย้ายการสร้างไปยัง value_type เหตุใดโค้ดของเราด้านบนจึงเลือกโอเวอร์โหลดนี้และไม่ใช่ unordered_map :: value_type overload อย่างที่คาดหวังมากที่สุด
คำตอบจ้องคุณตรงหน้า: unordered_map :: value_type เป็นคู่ < const int, std :: shared_ptr> และคอมไพเลอร์จะคิดว่าคู่ < int , std :: shared_ptr> ไม่สามารถแปลงได้ ดังนั้นคอมไพลเลอร์จึงเลือกการโอเวอร์โหลดการอ้างอิงสากลของการย้ายและทำลายต้นฉบับแม้ว่าโปรแกรมเมอร์จะไม่ใช้ std :: move () ซึ่งเป็นแบบแผนทั่วไปในการระบุว่าคุณโอเคกับการที่ตัวแปรถูกทำลาย ดังนั้นแทรกพฤติกรรมทำลายในความเป็นจริงที่ถูกต้องตามที่ C ++ 11 มาตรฐานและคอมไพเลอร์พี่ที่ไม่ถูกต้อง
ตอนนี้คุณคงเห็นแล้วว่าทำไมฉันถึงใช้เวลาสามวันในการวินิจฉัยจุดบกพร่องนี้ มันไม่ชัดเจนเลยในฐานรหัสขนาดใหญ่ที่ประเภทที่ถูกแทรกลงใน unordered_map คือ typedef ที่กำหนดไว้ห่างไกลในแง่ของซอร์สโค้ดและไม่เคยเกิดขึ้นกับใครเพื่อตรวจสอบว่า typedef เหมือนกับ value_type หรือไม่
ดังนั้นคำถามของฉันเกี่ยวกับ Stack Overflow:
เหตุใดคอมไพเลอร์รุ่นเก่าจึงไม่ทำลายตัวแปรที่แทรกเช่นคอมไพเลอร์รุ่นใหม่ ฉันหมายความว่าแม้แต่ GCC 4.7 ก็ไม่ทำเช่นนี้และเป็นไปตามมาตรฐานที่ค่อนข้างดี
ปัญหานี้เป็นที่รู้จักกันอย่างแพร่หลายหรือไม่เพราะการอัพเกรดคอมไพเลอร์จะทำให้โค้ดที่ใช้ทำงานหยุดทำงานกะทันหันหรือไม่?
คณะกรรมการมาตรฐาน C ++ ตั้งใจให้เกิดพฤติกรรมนี้หรือไม่?
คุณจะแนะนำให้แก้ไข unordered_map :: insert () อย่างไรเพื่อให้พฤติกรรมดีขึ้น ฉันถามสิ่งนี้เพราะหากมีการสนับสนุนที่นี่ฉันตั้งใจที่จะส่งพฤติกรรมนี้เป็นหมายเหตุ N ถึง WG21 และขอให้พวกเขาปรับใช้พฤติกรรมที่ดีขึ้น
4.9.0 20131223 (experimental)
ตามลำดับ ผลลัพธ์คือa.second is now 0x2074088
(หรือคล้ายกัน) สำหรับฉัน
a
ไม่ใช่ ควรทำสำเนา นอกจากนี้พฤติกรรมนี้ขึ้นอยู่กับ stdlib ไม่ใช่คอมไพเลอร์