Tôi vừa mất ba ngày trong cuộc đời mình để tìm ra một lỗi rất lạ trong đó unardered_map :: insert () phá hủy biến bạn chèn. Hành vi rất không rõ ràng này chỉ xảy ra trong các trình biên dịch gần đây: Tôi thấy rằng clang 3.2-3.4 và GCC 4.8 là những trình biên dịch duy nhất chứng minh "tính năng" này.
Dưới đây là một số mã giảm từ cơ sở mã chính của tôi, chứng tỏ sự cố:
#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;
}
Tôi, có lẽ giống như hầu hết các lập trình viên C ++, mong đợi đầu ra trông giống như sau:
a.second is 0x8c14048
a.second is now 0x8c14048
Nhưng với clang 3.2-3.4 và GCC 4.8, tôi nhận được điều này thay thế:
a.second is 0xe03088
a.second is now 0
Điều này có thể không có ý nghĩa gì, cho đến khi bạn kiểm tra kỹ lưỡng các tài liệu cho unardered_map :: insert () tại http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ trong đó quá tải số 2 là:
template <class P> pair<iterator,bool> insert ( P&& val );
Đó là một quá tải di chuyển tham chiếu phổ quát tham lam, sử dụng bất kỳ thứ gì không khớp với bất kỳ quá tải nào khác và chuyển cấu trúc nó thành một value_type. Vậy tại sao đoạn mã của chúng ta ở trên lại chọn quá tải này, chứ không phải là quá tải không có thứ tự_map :: value_type như hầu hết mọi người mong đợi?
Câu trả lời khiến bạn nhìn chằm chằm vào mặt: unsrdered_map :: value_type là một cặp < const int, std :: shared_ptr> và trình biên dịch sẽ nghĩ chính xác rằng một cặp < int , std :: shared_ptr> không thể chuyển đổi. Do đó, trình biên dịch chọn quá tải tham chiếu phổ quát di chuyển, và điều đó sẽ phá hủy bản gốc, mặc dù lập trình viên không sử dụng std :: move () là quy ước điển hình để chỉ ra rằng bạn ổn với một biến bị hủy. Do đó, hành vi hủy chèn trên thực tế là đúng theo tiêu chuẩn C ++ 11 và các trình biên dịch cũ hơn không chính xác .
Bây giờ bạn có thể thấy lý do tại sao tôi mất ba ngày để chẩn đoán lỗi này. Nó hoàn toàn không rõ ràng trong một cơ sở mã lớn nơi mà kiểu được chèn vào unardered_map là một typedef được định nghĩa ở xa trong các thuật ngữ mã nguồn và không bao giờ có người kiểm tra xem typedef có giống với value_type hay không.
Vì vậy, các câu hỏi của tôi đối với Stack Overflow:
Tại sao các trình biên dịch cũ hơn không hủy các biến được chèn vào như các trình biên dịch mới hơn? Ý tôi là, ngay cả GCC 4.7 cũng không làm điều này, và nó tuân theo các tiêu chuẩn khá tốt.
Vấn đề này có được biết đến rộng rãi không, bởi vì chắc chắn việc nâng cấp trình biên dịch sẽ khiến mã đã từng hoạt động đột ngột ngừng hoạt động?
Ủy ban tiêu chuẩn C ++ có dự định hành vi này không?
Làm thế nào bạn đề xuất rằng không có thứ tự_map :: insert () được sửa đổi để cung cấp hành vi tốt hơn? Tôi hỏi điều này vì nếu có sự hỗ trợ ở đây, tôi dự định gửi hành vi này như một ghi chú N cho WG21 và yêu cầu họ thực hiện một hành vi tốt hơn.
4.9.0 20131223 (experimental)
tương ứng. Đầu ra là a.second is now 0x2074088
(hoặc tương tự) đối với tôi.
a
là không. Nó sẽ tạo ra một bản sao. Ngoài ra, hành vi này hoàn toàn phụ thuộc vào stdlib, không phải trình biên dịch.