C++禁止直接实例化的类是否应删除移动构造与移动赋值运算符
场景需求
- 禁止用户直接调用构造函数创建
Foo类实例,因此将构造函数设为私有 - 所有实例生命周期自动管理,不允许用户直接操作
Foo类裸实例,要求统一通过std::shared_ptr<Foo>访问实例
现有实现代码
#include <iostream> #include <memory> class Foo : public std::enable_shared_from_this<Foo> { private: // 禁止用户直接调用该构造函数创建实例 Foo(int num):num_(num) { std::cout << "Foo::Foo\n"; } public: Foo(const Foo&) = delete; Foo(Foo&&) = default; Foo& operator=(const Foo&) = delete; Foo& operator=(Foo&&) = default; public: ~Foo() { std::cout << "Foo::~Foo\n"; } int DoSth(){std::cout << "hello world" << std::endl; return 0;} std::shared_ptr<Foo> getPtr() { return shared_from_this();} static std::shared_ptr<Foo> Create() { Foo* foo = new Foo(5); return std::shared_ptr<Foo>(foo); } private: int num_; }; int main() { auto sp = Foo::Create(); sp->DoSth(); Foo& foo = *sp.get(); auto sp1 = foo.getPtr(); Foo foo1(std::move(foo)); std::cout << sp.use_count() << std::endl; }
核心疑问
- 是否应当将移动构造函数、移动赋值运算符标记为
deleted? - 顾虑场景:若一个线程正在调用移动构造函数,同时另一个线程调用类的成员函数,是否会引发线程安全问题?需要该场景下的合理设计建议。
设计建议
必须删除移动构造与移动赋值运算符
保留默认移动构造/赋值直接违背你最初的设计目标:
- 现有测试代码里已经可以写出
Foo foo1(std::move(foo));这种逻辑,直接在栈上构造出了脱离shared_ptr管控的独立Foo实例,这个实例的生命周期完全不受智能指针管理,出作用域会自动析构,完全绕过了你设计的生命周期管理规则。 - 一旦用户在这种非
shared_ptr托管的栈实例上调用getPtr(),会直接触发未定义行为:enable_shared_from_this内部的弱引用指针只有在对象被shared_ptr持有时才会被正确初始化,在栈对象上调用shared_from_this()要么直接抛出异常,要么出现野指针崩溃。
关于线程安全顾虑的说明
你担心的「移动构造和成员函数并发调用」的问题,本质不是移动构造本身的问题,核心逻辑如下:
- C++标准明确规定,多个线程在无外部同步的前提下,对同一个对象同时执行写操作(移动构造会修改源对象的内容,属于写操作)和读写访问(调用成员函数如果涉及成员读写)本身就是数据竞争,属于未定义行为,这是通用的并发规则,和你是否保留移动构造无关。
- 删除移动构造之后,用户根本无法写出从托管实例移动构造新独立对象的代码,从根源上杜绝了这种错误使用场景的出现。所有对
Foo实例的访问都必须通过shared_ptr完成,shared_ptr的引用计数操作本身是线程安全的,你只需要按照普通类的并发规则,在Foo的成员函数里对内部共享成员的访问做好线程同步即可,不会额外引入风险。
额外优化提示
你现在Create里先new再构造shared_ptr的写法能跑通,因为构造函数是私有的没法直接用std::make_shared,如果想省一次内存分配,可以加个私有的空标签结构体做跳板,用std::make_shared构造,不做这个优化也完全不影响功能。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

