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

标签 c++ gcc c++11 clang standards

我刚刚失去了三天的生命来追踪一个非常奇怪的错误,其中 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 标准,插入破坏行为实际上正确,而旧的编译器不正确

您现在可能明白为什么我花了三天时间来诊断这个错误。在插入到 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 header 。

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::forward 的地方错误地使用了 std::move

关于c++ - C++ 标准委员会是否打算在 C++11 中 unordered_map 破坏它插入的内容?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21520308/

相关文章:

c++ - 类内部 lambda 和 boost 函数

c++ - 命令设计模式的实现有一些错误?

c++ - 在 Windows 8 中阻止键盘播放/暂停按钮

c - 在 osx 上使用 gcc 编译 allegro5 程序时出错

c - gcc asm 内存引用过多

c++ - 为枚举重载代字号 "~"运算符

c++ - 有必要在向图形添加边的功能中进行检查

c++ - 用一串命令预加载 cin

windows - 如何使用依赖于 libiconv 的 MinGW 安装 go 包

c++ - GCC 4.8.1 std::to_string 错误