gcc C++标准委员会的意图是在C++11中unordered_map破坏它插入的内容吗?

kyks70gy  于 2023-10-19  发布在  其他
关注(0)|答案(2)|浏览(133)

我刚刚浪费了三天的时间来追踪一个非常奇怪的错误,其中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()文档,其中重载no 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>是不可转换的。因此,编译器选择了move通用引用重载,这会破坏原始的,尽管程序员没有使用std::move(),这是一个典型的约定,表明你可以接受一个变量被破坏。因此,按照C++11标准,插入销毁行为实际上是 * 正确的 *,而旧的编译器是 * 不正确的 *。
你现在可能明白为什么我花了三天时间来诊断这个bug了。在大型代码库中,插入unordered_map的类型是在源代码中定义的typedef,这一点一点也不明显,而且从来没有人想到要检查typedef是否与value_type相同。
所以我对Stack Overflow的问题:
1.为什么老的编译器不像新的编译器那样销毁插入的变量?我的意思是,即使是GCC 4.7也没有做到这一点,而且它非常符合标准。
1.这个问题是否广为人知,因为升级编译器肯定会导致过去工作的代码突然停止工作?

  1. C++标准委员会有意这样做吗?
    1.您建议如何修改unordered_map::insert()以给予更好的行为?我这样问是因为如果这里有支持,我打算将此行为作为N注解提交给WG 21,并要求他们实现更好的行为。
fcipmucu

fcipmucu1#

正如其他人在评论中指出的那样,事实上,“通用”构造函数并不总是从它的参数移动。如果参数真的是右值,它应该移动,如果是左值,它应该复制。
您观察到的总是移动的行为是libstdc中的一个bug,根据对该问题的评论,现在已修复。对于那些好奇的人,我看了一下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::forward的地方错误地使用了std::move

px9o7tmv

px9o7tmv2#

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

这是一个贪婪的通用引用移动重载,消耗任何不匹配任何其他重载的内容,并将其构造为value_type。
这就是有些人所说的“通用引用”,但实际上是“引用崩溃”。在你的例子中,参数是pair<int,shared_ptr<int>>类型的 * 左值 *,它将不会导致参数是一个右值引用,它 * 不应该 * 从它移动。
那么,为什么我们上面的代码选择了这个重载,而不是大多数人可能期望的unordered_map::value_type重载呢?
因为您和之前的许多其他人一样,误解了容器中的value_type*mapvalue_type(无论是有序的还是无序的)是pair<const K, T>,在您的情况下是pair<const int, shared_ptr<int>>。不匹配的类型消除了您可能期望的重载:

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

相关问题