必须同时删除拷贝构造函数和拷贝赋值运算符,否则编译器会自动生成默认的拷贝赋值函数,导致类看似不可拷贝实则可赋值;c++11起应使用=delete显式禁用,且需注意继承、模板、资源语义等细节。

为什么 delete 拷贝构造函数后类还是能被拷贝?
常见错误是只删了拷贝构造函数,却忘了拷贝赋值运算符。C++11 起,delete 一个不等于自动禁用另一个——两者必须都显式删除,否则编译器会生成默认的拷贝赋值函数,导致“看似不可拷贝实则可赋值”。
典型现象:MyClass a; MyClass b = a; 编译失败(正确),但 b = a; 却能通过(踩坑)。
- 必须同时声明并
deleteMyClass(const MyClass&)和operator=(const MyClass&) - 若类有基类或成员含拷贝构造/赋值,它们也需满足不可拷贝约束,否则派生/组合时可能隐式调用成功
- 移动操作不受影响——
delete拷贝不会自动禁用移动;如需禁用移动,也要单独delete移动构造和移动赋值
C++11 以后最简写法:用 = delete 显式禁止
别手写空实现或私有化加友元,那是 C++98 的过时方案。现代 C++ 直接在类内用 = delete 就行,语义清晰、编译期报错明确。
示例:
立即学习“C++免费学习笔记(深入)”;
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
-
= delete必须写在类定义内部(头文件中),不能只在 .cpp 里声明 - 如果类有继承关系,基类已
= delete拷贝操作,派生类仍需显式声明(否则编译器可能生成默认版本) - 注意:
= default和= delete不能同时存在同一函数
哪些场景下必须手动禁用拷贝?
不是所有类都需要不可拷贝;滥用 = delete 反而限制接口灵活性。真正需要时,通常对应资源独占语义:
- 封装了裸指针、文件句柄、socket、mutex 等非共享资源的对象
- 设计为单例或全局唯一实例(如日志器、配置管理器)
- 作为 RAII 容器持有唯一所有权(如
std::unique_ptr的语义延伸) - 某些 STL 容器适配器(如
std::stack默认底层容器是std::deque,本身可拷贝;但若你用std::unique_ptr做成员,就得同步禁用拷贝)
容易忽略的兼容性细节
旧代码迁移到 C++11+ 时,常因宏或条件编译漏删拷贝函数。更隐蔽的问题是:模板推导或 SFINAE 场景下,= delete 函数仍参与重载决议,只是匹配后报错——这可能导致意外的编译失败而非静默跳过。
- 若类被用于模板参数(如
std::vector<noncopyable></noncopyable>),需确保它支持移动(否则容器扩容失败) - 使用
auto推导返回值时,若函数返回NonCopyable对象,且未启用返回值优化(RVO)或移动语义,编译会直接失败 - 某些 IDE 或静态分析工具对
= delete的诊断支持不全,建议用-Wdeleted-function(GCC/Clang)确认是否真被触发










