繁体   English   中英

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

[英]Is it intended by the C++ standards committee that in C++11 unordered_map destroys what it inserts?

我失去了三天的生命追踪一个非常奇怪的错误,其中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 ,std :: shared_ptr>是不可转换的。 因此编译器会选择移动通用引用重载,并且会破坏原始数据, 尽管程序员不使用std :: move(),这是表示你可以使变量被破坏的典型约定。 因此,根据C ++ 11标准,插入破坏行为实际上是正确的 ,并且较旧的编译器是不正确的

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

所以我对Stack Overflow的问题:

  1. 为什么较旧的编译器不会破坏像新编译器那样插入的变量? 我的意思是,即使是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。

这是有些人称之为普遍参考 ,但实际上是参考崩溃 在您的情况下,参数是类型pair<int,shared_ptr<int>>左值 ,它不会导致参数为右值引用,并且不应该从它移动。

那么为什么我们上面的代码选择了这个重载,而不是大多数人所期望的unordered_map :: value_type重载?

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM