【发布时间】:2014-02-26 12:32:59
【问题描述】:
我刚刚失去了三天的生命来追踪一个非常奇怪的错误,其中 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
但是使用 clang 3.2-3.4 和 GCC 4.8 我得到了这个:
a.second is 0xe03088
a.second is now 0
这可能没有意义,直到您仔细检查 http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ 的 unordered_map::insert() 文档,其中第 2 号重载是:
template <class P> pair<iterator,bool> insert ( P&& val );
这是一个贪婪的通用引用移动重载,消耗与任何其他重载不匹配的任何内容,并将其移动构造到 value_type。那么为什么我们上面的代码选择了这个重载,而不是大多数人所期望的 unordered_map::value_type 重载呢?
答案让你眼前一亮:unordered_map::value_type 是一对const int, std::shared_ptr> 编译器会正确地认为一对int em>, std::shared_ptr> 不可转换。因此,编译器选择移动通用引用重载,这会破坏原始的,尽管程序员不使用 std::move() 这是表明您可以接受变量被破坏的典型约定。因此,根据 C++11 标准,插入破坏行为实际上是正确的,而旧的编译器是不正确的。
您现在可能明白为什么我花了三天时间来诊断这个错误。在插入到 unordered_map 中的类型是在源代码术语中定义得很远的 typedef 的大型代码库中一点也不明显,而且任何人从未想过检查 typedef 是否与 value_type 相同。
所以我对 Stack Overflow 的问题:
为什么旧编译器不会像新编译器那样破坏插入的变量?我的意思是,即使 GCC 4.7 也没有这样做,而且它非常符合标准。
这个问题是否广为人知,因为升级编译器肯定会导致以前可以工作的代码突然停止工作?
C++ 标准委员会是否有意采取这种行为?
您如何建议修改 unordered_map::insert() 以提供更好的行为?我问这个是因为如果这里有支持,我打算将此行为作为 N 注释提交给 WG21,并要求他们实施更好的行为。
【问题讨论】:
-
仅仅因为它使用通用 ref 并不意味着插入的值总是被移动 - 它应该只对右值这样做,而普通的
a不是。它应该制作一个副本。此外,这种行为完全取决于标准库,而不是编译器。 -
这似乎是库实现中的一个错误
-
“因此,根据 C++11 标准,插入破坏行为实际上是正确的,而旧的编译器是不正确的。”对不起,但你错了。您是从 C++ 标准的哪个部分得到这个想法的? BTW cplusplus.com 不是官方的。
-
我无法在我的系统上重现这个,我分别使用 gcc 4.8.2 和
4.9.0 20131223 (experimental)。对我来说,输出是a.second is now 0x2074088(或类似的)。 -
This was GCC bug 57619,4.8 系列中的回归,在 2013-06 中针对 4.8.2 进行了修复。
标签: c++ gcc c++11 clang standards