含std::mutex的Track类对象存入std::unordered_map时的编译错误求助
看起来你遇到的问题核心是std::mutex不可拷贝也不可移动,导致你的Track类失去了默认的拷贝/移动语义,而std::unordered_map的常用操作(比如emplace、operator[])依赖这些语义,所以编译失败了。咱们一步步拆解问题,再给出解决办法:
为什么会报错?
首先明确一个关键规则:当类中包含std::mutex这类“非拷贝/非移动”成员时,C++会自动隐式删除该类的所有拷贝、移动相关的构造函数和赋值运算符。这直接导致了你的几个操作全部失败:
commonData.mTracks[trackId] = Track{trackId};:operator[]会先尝试默认构造Track(若key不存在),再执行赋值操作,但Track的赋值运算符已被隐式删除,因此报错。emplace系列操作:无论是emplace(trackId, Track{trackId})还是emplace({trackId, Track{trackId}}),都会尝试将Track对象拷贝/移动到unordered_map的内部存储中,而Track的拷贝/移动能力已丢失,导致模板实例化失败。
另外还要提醒你:std::unordered_map本身是线程不安全的,即使你解决了编译问题,多个线程同时修改map(插入、删除元素)也会导致数据竞争、迭代器失效等运行时问题,后续需要给map的访问加锁。
解决方案(按推荐程度排序)
方案1:在unordered_map中存储std::unique_ptr<Track>(最推荐)
std::unique_ptr是可移动的,它可以管理Track对象的生命周期,同时完全避免Track对象的拷贝/移动操作,完美适配你的场景。
修改后的代码示例:
// 调整unordered_map的类型 std::unordered_map<track_id_t, std::unique_ptr<Track>> mTracks; // 插入Track对象(原地构造,无拷贝/移动) { std::lock_guard<std::mutex> map_lock(commonData.map_mutex); // 给map加锁,避免多线程竞争 if (commonData.mTracks.find(trackId) == commonData.mTracks.end()) { commonData.mTracks.emplace(trackId, std::make_unique<Track>(trackId)); } } // 获取Track对象并调用helper函数 { std::lock_guard<std::mutex> map_lock(commonData.map_mutex); auto it = commonData.mTracks.find(trackId); if (it != commonData.mTracks.end()) { Track& t = *(it->second); t.helper1(); // 或t.helper2(),内部的mutex正常工作 } }
这个方案的优势:
- 完全规避
Track的拷贝/移动问题,代码简洁安全 - 保留了
Track类原有的线程安全设计(每个Track的mutex独立保护自身数据) - 给
unordered_map加锁后,解决了多线程修改map的竞争问题
方案2:给Track类手动实现移动语义(不推荐,仅特殊场景使用)
如果你坚持要在unordered_map中直接存储Track对象,需要手动实现Track的移动构造和移动赋值运算符,但因为std::mutex不可移动,需要用std::optional包装mutex来实现可移动性,这个方案复杂度高且有风险:
class Track { private: int mTrackId; std::optional<std::mutex> track_under_process_mutex; // 用optional包装mutex,使其可移动 // 其他成员 public: // 移动构造函数 Track(Track&& other) noexcept : mTrackId(other.mTrackId), track_under_process_mutex(std::move(other.track_under_process_mutex)) {} // 移动赋值运算符 Track& operator=(Track&& other) noexcept { if (this != &other) { mTrackId = other.mTrackId; track_under_process_mutex = std::move(other.track_under_process_mutex); } return *this; } // 带参构造函数(初始化optional中的mutex) Track(int trackId) : mTrackId(trackId), track_under_process_mutex(std::in_place) {} bool helper1(void) { std::lock_guard<std::mutex> lock(*track_under_process_mutex); // 解引用optional获取mutex // 执行处理逻辑 return true; } // helper2同理 bool helper2(void) { std::lock_guard<std::mutex> lock(*track_under_process_mutex); // 执行处理逻辑 return true; } };
插入元素时使用C++17支持的try_emplace(原地构造,避免移动):
std::lock_guard<std::mutex> map_lock(commonData.map_mutex); commonData.mTracks.try_emplace(trackId, trackId); // 原地构造Track,无拷贝/移动
这个方案的风险:
- 移动后的原
Track对象的mutex会处于未激活状态,若后续误调用其helper函数会导致未定义行为 - 增加了代码复杂度,
std::optional的解引用容易出错
方案3:存储std::shared_ptr<Track>(适合多线程共享所有权场景)
如果你的Track对象需要被多个线程/模块共享所有权(比如需要长期持有引用,不用担心生命周期),可以用std::shared_ptr替代unique_ptr,用法和方案1几乎一致:
std::unordered_map<track_id_t, std::shared_ptr<Track>> mTracks; // 插入 std::lock_guard<std::mutex> map_lock(commonData.map_mutex); commonData.mTracks.emplace(trackId, std::make_shared<Track>(trackId)); // 获取并调用函数 std::lock_guard<std::mutex> map_lock(commonData.map_mutex); auto it = commonData.mTracks.find(trackId); if (it != commonData.mTracks.end()) { it->second->helper1(); }
最后提醒
无论使用哪种方案,一定要给std::unordered_map的访问加独立的mutex,多个线程同时修改map会导致隐蔽的运行时错误,比如数据损坏、迭代器失效等。
内容来源于stack exchange

