如何在std::unordered_map中存储含std::atomic的结构体作为值?
std::atomic在std::unordered_map中存储的编译问题解析
问题现象
std::atomic可以直接作为值存储在std::unordered_map中,也能通过std::make_pair存入std::pair类型的值,但以下两种场景会编译失败:
- 使用初始化列表向存储
std::pair的unordered_map插入元素 - 向存储含
std::atomic的自定义结构体的unordered_map插入元素
对应的示例代码:
#include <atomic> #include <string> #include <unordered_map> struct LexemData { unsigned int id{}; std::atomic<size_t> counter{}; }; std::unordered_map<std::string, std::atomic<size_t>> dict1; std::unordered_map<std::string, std::pair<unsigned int, std::atomic<size_t>>> dict2; std::unordered_map<std::string, LexemData> dict3; int main() { std::string lexem("lexem"); dict2.insert({ lexem, std::make_pair(0, 0) }); // 编译成功 dict2.insert({ lexem, {0, 0} }); // 编译失败(1) dict3.insert({ lexem, LexemData {0,0} }); // 编译失败(2) }
核心原因
std::atomic是不可复制构造、不可复制赋值的类型,但它支持移动构造和移动赋值(C++11及以后标准)。编译失败的本质是触发了std::atomic的复制操作:
- 对于
dict2.insert({ lexem, {0, 0} }):初始化列表{0,0}会先构造一个临时的std::pair对象,随后容器尝试通过复制构造将这个临时对象存入容器,而pair内的std::atomic无法被复制,导致编译错误。 - 对于
dict3.insert({ lexem, LexemData {0,0} }):临时构造的LexemData对象会被尝试复制到容器中,结构体包含的std::atomic无法复制,因此编译失败。
而dict2.insert({ lexem, std::make_pair(0, 0) })能成功,是因为std::make_pair直接构造的pair是右值,容器会使用移动构造将其存入,避免了复制操作。
解决方案
1. 修复初始化列表插入失败(场景1)
使用emplace代替insert,直接在容器内部就地构造元素,完全避免临时对象的复制:
// 用piecewise_construct构造pair的两个成员,就地初始化 dict2.emplace( std::piecewise_construct, std::forward_as_tuple(lexem), std::forward_as_tuple(0, std::atomic<size_t>(0)) );
或者显式构造右值pair并移动插入:
dict2.insert({ lexem, std::pair<unsigned int, std::atomic<size_t>>(0, 0) });
2. 修复自定义结构体插入失败(场景2)
方法一:利用移动语义插入
将临时结构体转为右值,触发移动构造:
dict3.insert({ lexem, std::move(LexemData{0, 0}) });
方法二:让结构体支持移动构造(显式声明)
虽然C++编译器会为包含可移动成员的结构体自动生成移动构造函数,但显式声明可以避免编译器因其他因素(如存在自定义构造函数)不生成的情况:
struct LexemData { unsigned int id{}; std::atomic<size_t> counter{}; // 显式默认移动构造和移动赋值 LexemData(LexemData&&) = default; LexemData& operator=(LexemData&&) = default; };
之后即可直接用emplace就地构造元素,彻底避免复制:
dict3.emplace(lexem, 0, 0);
内容的提问来源于stack exchange,提问作者Damir Tenishev
相关产品推荐
相关产品推荐

