如何在仅可移动类中添加std::mutex成员且无需手动编写移动构造?
问题:含std::mutex的仅可移动类编译失败的解决办法
问题背景
类B设计为不可拷贝、仅可移动,此前依赖编译器自动生成的移动构造函数与移动赋值运算符正常工作。但添加std::mutex成员后,由于std::mutex既不可拷贝也不可移动,导致代码无法编译。
示例代码
#include <iostream> #include <string> #include <vector> #include <mutex> class C{ public: C(){} C(C&& c2){ } C& operator=(C&& c2){ return *this; } // 设计要求:禁止拷贝,仅允许移动 }; class B{ public: int a=0; C c; // 10+ 简单成员变量 // private: std::mutex mutexx; // 取消注释后编译失败 }; int main(){ B b1; B b2; b1=std::move(b2); }
现有不理想方案分析
1. 手动编写移动函数
class B{ public: int a=0; C c; // 10+ 简单成员变量 private: std::mutex mutexx; public: B(B&& b2){ this->c=std::move(b2.c); // 手动拷贝/移动其他所有成员——繁琐且难以维护 // 故意忽略mutex的移动/拷贝 } B& operator=(B&& b2){ /* 同样需要手动处理所有成员 */ } };
缺点:需要手动处理所有非mutex成员,当类成员变更时容易遗漏,维护成本高。
2. 使用std::unique_ptr<std::mutex>
缺点:
- 代码可读性下降,需要将
mutexx.lock()改为mutexx->lock(),重构成本高 - 引入额外堆内存分配与间接访问的CPU开销
- 增加了空指针检查的潜在需求
3. 封装mutex并编写空移动函数
将std::mutex封装到自定义类中,编写空的移动构造与赋值函数,但这种方案的合理性存疑——移动操作的语义不明确,且可能隐藏潜在的同步问题。
可行解决方案
方案一:利用默认成员移动简化手动实现(C++11及以上)
显式编写移动函数,但通过成员初始化列表和std::move复用编译器的默认移动逻辑,仅跳过mutex的处理:
class B{ public: int a=0; C c; // 10+ 简单成员变量 private: std::mutex mutexx; public: // 移动构造:默认初始化新的mutex,移动其他所有业务成员 B(B&& b2) noexcept : a(std::move(b2.a)), c(std::move(b2.c)) // 依次列出其他非mutex成员,用std::move包裹 {} // 移动赋值:移动业务成员,当前对象的mutex保持不变 B& operator=(B&& b2) noexcept { if (this != &b2) { a = std::move(b2.a); c = std::move(b2.c); // 处理其他非mutex成员 } return *this; } // 显式删除拷贝操作,符合设计要求 B(const B&) = delete; B& operator=(const B&) = delete; };
优势:严格遵循类的设计语义,移动操作仅转移业务资源,mutex作为同步资源由每个对象独立持有,避免潜在的同步问题。
方案二:通过基类封装mutex并启用默认移动(C11及以上,C20可优化)
将mutex封装到基类中,为基类编写空的移动函数(因为mutex不需要移动),派生类即可自动生成移动构造与赋值运算符:
// 封装mutex的基类 class MutexHolder { protected: std::mutex mutexx; public: MutexHolder() = default; // 空移动构造:新对象创建自己的mutex,原对象的mutex可正常销毁 MutexHolder(MutexHolder&&) noexcept {} // 空移动赋值:保持当前对象的mutex不变 MutexHolder& operator=(MutexHolder&&) noexcept { return *this; } // 禁止拷贝 MutexHolder(const MutexHolder&) = delete; MutexHolder& operator=(const MutexHolder&) = delete; }; // 派生类B class B : public MutexHolder { public: int a=0; C c; // 10+ 简单成员变量 // 编译器自动生成移动构造与赋值:移动所有派生类成员,基类移动为空操作 B(B&&) = default; B& operator=(B&&) = default; // 禁止拷贝 B(const B&) = delete; B& operator=(const B&) = delete; };
优势:无需手动处理派生类的成员,当类成员变更时自动适配,维护成本低。移动语义清晰:每个B对象独立持有自己的mutex,移动操作仅转移业务资源,原对象的mutex可安全销毁。
C++20优化:可以给MutexHolder添加[[no_unique_address]]属性(如果基类只有mutex成员),避免额外的内存开销:
class [[no_unique_address]] MutexHolder { // 内容同上 };
内容的提问来源于stack exchange,提问作者cppBeginner
相关产品推荐
相关产品推荐

