gcc与clang的unordered_map::insert实现差异及STL标准规定问询
现象原因
这个差异本质是gcc默认使用的libstdc标准库和clang默认使用的libc标准库,对unordered_map::insert重载的匹配逻辑实现不同,并非gcc和clang编译器本身的语法解析逻辑差异。
std::unordered_map的insert方法存在两个相关重载:
- 非模板重载:
std::pair<iterator, bool> insert(const value_type& value);
其中value_type就是std::pair<const Key, T>,也就是你代码中变量e的类型。 - C++11引入的模板转发重载:
template<class P> std::pair<iterator, bool> insert(P&& value);
设计初衷是支持将可转换为value_type的参数完美转发构造,减少不必要的拷贝开销。
你原始代码中的e是非const的value_type左值,两个标准库的重载匹配逻辑出现了分歧:
- libc++(clang默认标准库)对模板重载加了SFINAE约束:只有当
P不是value_type类型时,模板重载才会参与重载决议。因此这种场景下非模板的const value_type&重载优先级更高,会被优先选中。 - 你使用的gcc 8.3对应的旧版本libstdc++没有添加这个约束,模板重载会将
P推导为value_type&,匹配度比需要额外做const转换的非模板重载更高,因此会选中模板转发重载。
而两个重载的执行逻辑差异直接导致了拷贝次数不同:
- 非模板的
insert(const value_type&)重载:执行时会先检查待插入的key是否已存在,如果已经存在直接返回,不会触发拷贝操作。只有第一次插入成功时才会把e拷贝到容器中,因此总共只产生1次拷贝。 - 模板转发的
insert(P&&)重载:语义要求先将传入的参数完美转发构造出一个value_type实例,再尝试插入。哪怕后续检查发现key重复、插入失败,这次构造产生的拷贝也已经发生。因此循环10次会触发10次拷贝。
当你把e声明为const类型后,传入insert的参数是const value_type左值,此时非模板的const value_type&重载是完全匹配,优先级高于模板重载,两个标准库都会选中这个非模板重载,输出自然就一致了。
标准规范说明
C++标准对这类插入行为是有明确定义的:
- 对于
insert(const value_type&)重载:仅当插入成功(key不存在)时,才会将输入的value拷贝到容器中,插入失败时不会产生拷贝操作。 - 对于模板转发的
insert(P&&)重载:C++17之后明确要求该重载仅当std::is_constructible_v<value_type, P&&>为true时才参与重载决议,同时补充规定当传入参数就是value_type类型时,非模板重载优先级更高,避免不必要的转发构造。
你遇到的差异是旧版本libstdc的实现不符合标准规范导致的,新版本libstdc已经修复了这个问题,和libc++的行为保持一致。
内容的提问来源于stack exchange,提问作者konchy
相关产品推荐
相关产品推荐

