C++如何实现不可拷贝类?(delete拷贝构造函数)

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

C++如何实现不可拷贝类?(delete拷贝构造函数)

为什么 delete 拷贝构造函数后类还是能被拷贝?

常见错误是只删了拷贝构造函数,却忘了拷贝赋值运算符。C++11 起,delete 一个不等于自动禁用另一个——两者必须都显式删除,否则编译器会生成默认的拷贝赋值函数,导致“看似不可拷贝实则可赋值”。

典型现象:MyClass a; MyClass b = a; 编译失败(正确),但 b = a; 却能通过(踩坑)。

  • 必须同时声明并 delete MyClass(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)确认是否真被触发

类不可拷贝这件事,关键不在“删掉两个函数”,而在理解对象生命周期与资源语义的绑定关系。删得不干净,或删得不合时宜,都会让问题延迟到运行时或集成阶段才暴露。