如何在MS Concurrency::concurrent_unordered_map中存储含std::atomic的结构体?
微软的Concurrency::concurrent_unordered_map未提供emplace方法,导致无法借鉴普通std::unordered_map存储std::atomic的解决方案。现有代码因LexemData包含不可拷贝的std::atomic成员,调用insert时触发C2665编译错误,需求是在不引入额外库(如oneapi::tbb)的前提下,实现多线程安全的计数器递增。
原代码示例:
#include <atomic> #include <string> #include <concurrent_unordered_map.h> struct LexemData { unsigned int id{}; std::atomic<size_t> counter{}; }; int main() { Concurrency::concurrent_unordered_map<std::string, LexemData> dict; std::string lexem("lexem"); dict.insert({ lexem, LexemData {0,0} }); // Doesn't compile }
解决方案1:存储动态分配的对象指针
既然无法直接存储不可拷贝的对象,可改用存储std::unique_ptr<LexemData>或std::shared_ptr<LexemData>,容器存储的是可拷贝/移动的指针类型,避免拷贝std::atomic成员。
修改后代码:
#include <atomic> #include <string> #include <memory> #include <concurrent_unordered_map.h> struct LexemData { unsigned int id{}; std::atomic<size_t> counter{}; }; int main() { Concurrency::concurrent_unordered_map<std::string, std::unique_ptr<LexemData>> dict; std::string lexem("lexem"); // 构造对象后转移所有权插入容器 auto data = std::make_unique<LexemData>(LexemData{0, 0}); dict.insert({ lexem, std::move(data) }); // 多线程递增计数器示例 if (auto it = dict.find(lexem); it != dict.end()) { it->second->counter.fetch_add(1, std::memory_order_relaxed); } }
说明:concurrent_unordered_map的insert支持移动语义,std::unique_ptr的所有权转移不会触发LexemData的拷贝;find操作本身线程安全,atomic成员的递增操作天然原子,无需额外锁。
解决方案2:利用pair的移动构造插入
std::atomic类型支持移动构造(C++11及以后),因此LexemData会自动生成移动构造函数。可以直接构造std::pair<const std::string, LexemData>并移动插入,避免拷贝操作。
修改后代码:
#include <atomic> #include <string> #include <concurrent_unordered_map.h> struct LexemData { unsigned int id{}; std::atomic<size_t> counter{}; }; int main() { Concurrency::concurrent_unordered_map<std::string, LexemData> dict; std::string lexem("lexem"); // 直接构造pair并移动插入,避免拷贝LexemData dict.insert(std::make_pair(std::move(lexem), LexemData{0, 0})); // 多线程递增计数器示例 if (auto it = dict.find("lexem"); it != dict.end()) { it->second.counter.fetch_add(1, std::memory_order_relaxed); } }
说明:通过std::move转移字符串和LexemData的所有权,全程不会触发std::atomic的拷贝,符合容器的插入要求。
解决方案3:自定义可拷贝的原子封装(不推荐)
如果必须存储值类型,可自定义封装std::atomic的类,通过显式拷贝构造复制原子值,但这种方式会破坏原子操作的语义,仅适用于插入操作单线程完成、后续仅做递增的场景。
修改后代码:
#include <atomic> #include <string> #include <concurrent_unordered_map.h> struct CopyableAtomicSizeT { std::atomic<size_t> value{}; // 拷贝构造:读取当前原子值快照 CopyableAtomicSizeT(const CopyableAtomicSizeT& other) : value(other.value.load()) {} // 赋值运算符 CopyableAtomicSizeT& operator=(const CopyableAtomicSizeT& other) { value.store(other.value.load()); return *this; } // 默认保留移动构造/赋值 CopyableAtomicSizeT(CopyableAtomicSizeT&&) = default; CopyableAtomicSizeT& operator=(CopyableAtomicSizeT&&) = default; // 封装原子递增接口 void fetch_add(size_t n, std::memory_order order = std::memory_order_seq_cst) { value.fetch_add(n, order); } }; struct LexemData { unsigned int id{}; CopyableAtomicSizeT counter{}; }; int main() { Concurrency::concurrent_unordered_map<std::string, LexemData> dict; std::string lexem("lexem"); dict.insert({ lexem, LexemData {0,0} }); // 多线程递增计数器示例 if (auto it = dict.find(lexem); it != dict.end()) { it->second.counter.fetch_add(1); } }
说明:拷贝操作会读取原子值的瞬时快照,多线程插入时可能导致数据不一致,仅作为特殊场景下的备选方案。
内容的提问来源于stack exchange,提问作者Damir Tenishev

