我刚刚度过了三天的时间,一直在寻找一个非常奇怪的错误,在这个错误中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是pair < const int,std :: shared_ptr>,编译器会正确地认为pair < int,std :: shared_ptr>是不可转换的。因此,尽管程序员没有使用std :: move(),但编译器选择了移动通用引用重载,并且破坏了原始重载,这是典型的约定,用于表明您可以使用被破坏的变量。因此,按照C ++ 11标准,插入破坏行为实际上是正确的,而较早的编译器是错误的。
您现在可能知道为什么我花了三天的时间来诊断此错误。在大型代码库中,这一点都不明显,因为插入源代码术语中定义的typedef是插入unordered_map中的类型,并且从未有人检查过typedef是否与value_type相同。
所以我对堆栈溢出的问题是:
为什么较旧的编译器不会破坏像较新的编译器那样插入的变量?我的意思是,即使GCC 4.7也没有做到这一点,并且符合标准。
这个问题是否广为人知,因为肯定会升级编译器会导致以前可以工作的代码突然停止工作?
C ++标准委员会是否打算这样做?
您如何建议修改unordered_map :: insert()以获得更好的行为?我之所以这样问是因为,如果这里有支持,我打算将此行为作为N笔记提交给WG21,并请他们实施更好的行为。
a
不会。它应该制作一个副本。同样,此行为完全取决于stdlib,而不取决于编译器。