必须同时禁用拷贝构造、拷贝赋值、移动构造和移动赋值运算符,否则资源管理类可能因浅拷贝导致double free;基类禁用不自动传导至派生类,需手动重复四句=delete。

NonCopyable(const NonCopyable&) = delete; 和 NonCopyable& operator=(const NonCopyable&) = delete; 是最直接、最安全的写法。只禁用其中一个,另一条路径仍可绕过限制;不同时禁用,就等于没禁。
必须同时禁用拷贝构造和赋值运算符
编译器不会帮你“连带禁止”——禁了 NonCopyable(const NonCopyable&) = delete;,但没禁 operator=,那 obj1 = obj2 依然能通过;反之,禁了赋值却留着拷贝构造,NonCopyable c2(c1) 或 func(c1)(按值传参)照样跑得起来。
常见错误现象:
- 代码编译通过,但运行时出现 double free 或内存踩踏
- std::vector
.push_back() 在某些编译器下静默调用移动构造,而非报错
所以这两行必须成对出现:
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
别忘了移动语义:否则 std::vector 可能悄悄绕过
如果类有资源管理逻辑(比如持有 int*、FILE*、std::mutex),且你没显式禁用移动操作,C++11/14 编译器可能隐式生成 NonCopyable(NonCopyable&&) 和 operator=(NonCopyable&&)。它们默认执行位拷贝,等同于浅拷贝。
后果很隐蔽:
-
std::vector可能触发移动而非拷贝,在 clang/gcc 不同版本下行为不一致.push_back(x) - 对象析构时两个指针指向同一块内存,double free
稳妥做法是四句一起写:
NonCopyable(const NonCopyable&) = delete; NonCopyable& operator=(const NonCopyable&) = delete; NonCopyable(NonCopyable&&) = delete; NonCopyable& operator=(NonCopyable&&) = delete;
继承体系里基类 = delete 不会自动传导
即使你在基类写了 Base(const Base&) = delete;,派生类仍可能意外提供公有的拷贝构造函数,比如:
class Derived : public Base {
public:
Derived(const Derived&) {} // ✅ 合法!绕过了 Base 的 delete
};
这会导致用户误以为 Derived 也不能拷贝,实际却可以。解决办法只有两个:
- 在每个需要禁用拷贝的派生类里,手动重复四句
= delete - 或统一用私有继承 + using 声明控制访问(更复杂,不推荐初学者)
宏定义(如 #define DISABLE_COPY(Class) Class(const Class&) = delete; ...)看似省事,但容易漏掉移动函数,且编译错误信息不如手写清晰。
为什么不用 private 声明+不定义的老办法
旧式写法:private: NonCopyable(const NonCopyable&);,不实现。它的问题在于:
- 错误发生在链接期而非编译期:代码能编译,到链接时报 undefined reference,定位困难
- 无法阻止友元函数或成员函数内部调用(
= delete是访问控制 + 禁用生成双重保障) - 不兼容移动语义的显式禁用逻辑
= delete 是 C++11 起的标准解法,错误信息明确(例如 use of deleted function 'NonCopyable::NonCopyable(const NonCopyable&)'),且被所有主流编译器严格支持。
真正麻烦的是资源清理时机和跨继承边界的约束一致性——禁用本身很简单,难的是确保所有子类、所有容器操作、所有模板实例化都尊重这个禁令。稍有遗漏,问题就藏在运行时。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











