C ++标准委员会打算在C ++ 11中使用unordered_map破坏它所插入的内容吗?

已解决:这是libstdc ++ <v4.8.2中的一个错误,如果系统上存在GCC v4.8和clang> v3.2将会使用它。 有关报告,请参阅http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57619 。 感谢凯西和布赖恩给出了正确的答案。 尼尔

原始问题:

我刚刚失去了三天的时间跟踪一个非常奇怪的bug,unordered_map :: insert()破坏了你插入的variables。 这种非常不明显的行为仅出现在最近的编译器中:我发现叮当3.2-3.4和GCC 4.8是唯一certificate这个“特性”的编译器。

下面是我的主要代码库中的一些简化代码,它演示了这个问题:

#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 

这可能没有任何意义,除非您仔细检查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。 那么为什么我们的代码select这个重载,而不是unordered_map :: value_type重载可能最期望?

答案盯着你:unordered_map :: value_type是一对< const int,std :: shared_ptr>,编译器会正确地认为一对< int ,std :: shared_ptr>是不可转换的。 因此,编译器select移动通用引用重载,并且破坏原始文件, 尽pipe程序员不使用std :: move(),这是一个典型的惯例,表示你可以使用variables被破坏。 因此,按照C ++ 11标准,插入破坏行为实际上是正确的 ,而较老的编译器是不正确的

你现在可以看到为什么我花了三天的时间来诊断这个bug。 在大代码库中,插入到unordered_map中的types是在源代码术语中远远定义的typedef,并且任何人都不会检查typedef是否与value_type相同。

所以我的问题堆栈溢出:

  1. 为什么较旧的编译器不能销毁像新编译器那样插入的variables? 我的意思是,甚至GCC 4.7也没有这样做,而且这个标准很符合标准。

  2. 这个问题广为人知,因为升级编译器肯定会导致曾经工作的代码突然停止工作?

  3. C ++标准委员会是否打算这样做?

  4. 你会如何修改unordered_map :: insert()以提供更好的行为? 我这样问,因为如果在这里有支持,我打算把这个行为作为一个N注释提交给WG21,并要求他们实施一个更好的行为。

正如其他人在评论中指出的那样,“普遍的”构造者实际上并不总是偏离其论点。 如果参数实际上是一个右值,它应该移动,如果它是左值,则复制。

你观察到的总是移动的行为是libstdc ++中的一个错误,现在根据对这个问题的评论来解决。 对于那些好奇的人,我看了一下g ++ – 4.8头文件。

bits/stl_map.h ,第598-603行

  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)); } 

bits/unordered_map.h ,行365-370

  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)); } 

后者是错误地使用std::move它应该使用std::forward

 template <class P> pair<iterator,bool> insert ( P&& val ); 

这是一个贪婪的通用引用移动重载,消耗任何不匹配任何其他重载,并将其构造成value_type。

这就是有些人所说的普遍引用 ,但实际上是引用崩溃 。 在你的情况下,参数是typespair<int,shared_ptr<int>>左值 ,它不会导致参数是一个右值引用,它不应该从它移动。

那么为什么我们的代码select这个重载,而不是unordered_map :: value_type重载可能最期望?

因为你和其他人一样,误解了容器中的value_type*map (无论是有序还是无序)的value_typepair<const K, T> ,在你的情况下是pair<const int, shared_ptr<int>> 。 不匹配的types消除了您可能期望的过载:

 iterator insert(const_iterator hint, const value_type& obj);