Is it intended by the C++ standards committee that in C++11 unordered_map destroys what it inserts?
我刚刚花了三天的时间来追踪一个非常奇怪的bug,在这个bug中无序的map::insert()会破坏您插入的变量。这种非常不明显的行为只出现在最近的编译器中:我发现clang 3.2-3.4和gcc 4.8是唯一演示这种"特性"的编译器。
下面是我的主代码库中的一些简化代码,它演示了这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #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++程序员一样,期望输出看起来像这样:
1 2 | a.second is 0x8c14048 a.second is now 0x8c14048 |
但是有了clang 3.2-3.4和gcc 4.8,我得到了:
1 2 | a.second is 0xe03088 a.second is now 0 |
这可能毫无意义,除非您仔细检查文档中是否存在无序的_map::insert(),网址为http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/,其中重载2是:
1 | template <class P> pair<iterator,bool> insert ( P&& val ); |
这是一个贪婪的通用引用move重载,使用任何与其他重载不匹配的内容,并将其构造为value_类型。那么,为什么上面的代码选择了这个重载,而不是像大多数人预期的那样无序的_map::value_类型重载?
答案直视你的脸:无序的值类型是一对
你现在可能知道为什么我花了三天时间诊断这个错误了。在一个大型代码库中,插入无序映射的类型是一个远在源代码术语中定义的typedef,这一点一点也不明显,而且任何人都没有想到检查typedef是否与值类型相同。
所以我的问题是堆栈溢出:
为什么旧的编译器不会像新的编译器那样破坏插入的变量?我的意思是,即使GCC4.7也不能做到这一点,而且它相当符合标准。
这个问题广为人知吗,因为升级编译器肯定会导致以前工作的代码突然停止工作?
C++标准委员会打算实施这种行为吗?
您如何建议修改无序的_map::insert()以提供更好的行为?我这样问是因为如果这里有支持,我打算将这一行为作为一个N注释提交给WG21,并要求他们实施一个更好的行为。
正如其他人在评论中指出的那样,"通用"构造函数实际上并不应该总是从它的参数开始移动。如果参数真的是右值,它应该移动,如果是左值,它应该复制。
你观察到的行为,总是在移动,是libstdc++中的一个bug,根据对这个问题的评论,这个bug现在被修复了。对于那些好奇的人,我看了一下G++-4.8的头。
1 2 3 4 5 6 | template<typename _Pair, typename = typename std::enable_if<std::is_constructible<value_type, _Pair&&>::value>::type> std::pair<iterator, bool> insert(_Pair&& __x) { return _M_t._M_insert_unique(std::forward<_Pair>(__x)); } |
1 2 3 4 5 6 | template<typename _Pair, typename = typename std::enable_if<std::is_constructible<value_type, _Pair&&>::value>::type> std::pair<iterator, bool> insert(_Pair&& __x) { return _M_h.insert(std::move(__x)); } |
后者错误地使用了
1 | template <class P> pair<iterator,bool> insert ( P&& val ); |
Which is a greedy universal reference move overload, consuming anything not matching any of the other overloads, and move constructing it into a value_type.
这就是一些人所说的普遍参考,但实际上是参考崩溃。在您的情况下,如果参数是
So why did our code above choose this overload, and not the
unordered_map::value_type overload as probably most would expect?
因为你和以前的许多人一样,误解了容器中的
1 | iterator insert(const_iterator hint, const value_type& obj); |