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

I've just lost three days of my life tracking down a very strange bug where unordered_map::insert() destroys the variable you insert. This highly non-obvious behaviour occurs in very recent compilers only: I found that clang 3.2-3.4 and GCC 4.8 are the only compilers to demonstrate this "feature".

Here's some reduced code from my main code base which demonstrates the issue:

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

I, like probably most C++ programmers, would expect output to look something like this:

a.second is 0x8c14048
a.second is now 0x8c14048

But with clang 3.2-3.4 and GCC 4.8 I get this instead:

a.second is 0xe03088
a.second is now 0

Which might make no sense, until you examine closely the docs for unordered_map::insert() at http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ where overload no 2 is:

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?

The answer stares you in the face: unordered_map::value_type is a pair<const int, std::shared_ptr> and the compiler would correctly think that a pair<int, std::shared_ptr> isn't convertible. Therefore the compiler chooses the move universal reference overload, and that destroys the original, despite the programmer not using std::move() which is the typical convention for indicating you are okay with a variable getting destroyed. Therefore the insert destroying behaviour is in fact correct as per the C++11 standard, and older compilers were incorrect.

You can probably see now why I took three days to diagnose this bug. It was not at all obvious in a large code base where the type being inserted into unordered_map was a typedef defined far away in source code terms, and it never occurred to anyone to check if the typedef was identical to value_type.

So my questions to Stack Overflow:

  1. Why do older compilers not destroy variables inserted like newer compilers? I mean, even GCC 4.7 doesn't do this, and it's pretty standards conforming.

  2. Is this problem widely known, because surely upgrading compilers will cause code which used to work to suddenly stop working?

  3. Did the C++ standards committee intend this behaviour?

  4. How would you suggest that unordered_map::insert() be modified to give better behaviour? I ask this because if there is support here, I intend to submit this behaviour as a N note to WG21 and ask them to implement a better behaviour.

6931 次浏览
template <class P> pair<iterator,bool> insert ( P&& val );

这是一个贪婪的通用引用 move 重载,消耗任何与其他重载不匹配的内容,然后将其构造成 value _ type。

这就是一些人所说的 通用参考文献,但实际上是 参考崩溃。在您的例子中,参数是 pair<int,shared_ptr<int>>类型的 Lvalue,它将 没有导致参数是一个右值引用,并且它将从 不应该移出。

So why did our code above choose this overload, and not the unordered_map::value_type overload as probably most would expect?

因为你和以前的许多人一样,误解了容器中的 value_type*mapvalue_type(无论是有序的还是无序的)是 pair<const K, T>,在您的例子中是 pair<const int, shared_ptr<int>>。类型不匹配消除了您可能期望的重载:

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

As others have pointed out in the comments, the "universal" constructor is not, in fact, supposed to always move from its argument. It's supposed to move if the argument is really an rvalue, and copy if it's an lvalue.

您观察到的总是在移动的行为是 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::forward的地方错误地使用了 std::move