在C++11及以上,用= delete禁用拷贝构造函数最干净明确,需同时禁用拷贝赋值运算符;仅私有化会导致友元/成员函数绕过、链接期错误等缺陷;禁用后按值传递、初始化、容器存储等操作均触发编译错误。

如何在C++11及以上禁用拷贝构造函数
直接用 = delete 是最干净、最明确的方式。编译器会在任何尝试拷贝该类型的上下文中报错,且错误信息清晰指向被删除的函数。
常见错误现象:忘记加 = delete 而只声明不定义,会导致链接期错误(undefined reference to 'ClassName::ClassName(const ClassName&)' ),而不是编译期报错,排查更困难。
实操建议:
- 在类内声明拷贝构造函数并紧跟
= delete,例如:class NonCopyable {<br>public:<br> NonCopyable() = default;<br> NonCopyable(const NonCopyable&) = delete;<br> NonCopyable& operator=(const NonCopyable&) = delete;<br>}; - 必须同时禁用拷贝赋值运算符(
operator=),否则对象仍可能被赋值后间接“复制” - 如果类有移动构造函数,禁用拷贝不会影响移动语义;但若只禁用拷贝而未定义移动操作,某些场景(如容器插入)会因无法拷贝也无法移动而编译失败
为什么不能只私有化拷贝构造函数(C++98/03风格)
将拷贝构造函数声明为 private 且不提供定义,曾是 C++98 的惯用法,但它存在严重缺陷:友元函数和类成员函数仍可调用它,导致禁用不彻底。
立即学习“C++免费学习笔记(深入)”;
使用场景:仅适用于极老项目或需兼容无 = delete 的编译器(如 VS2010 以前),现代代码应避免。
容易踩的坑:
- 私有化后,若类内某个成员函数意外调用了拷贝(比如传值返回临时对象),编译通过但逻辑出错
- 友元类或函数可绕过访问控制,完全失效
- 错误延迟到链接阶段,而非编译期,调试成本高
禁用后哪些操作会触发编译错误
只要涉及隐式或显式拷贝构造,编译器就会立即报错。不是运行时崩溃,而是编译失败,这是 = delete 的核心价值。
典型触发点:
- 用一个已存在对象初始化新对象:
NonCopyable a; NonCopyable b = a; - 函数参数按值传递:
void foo(NonCopyable x) { }→ 调用foo(a)报错 - 函数按值返回:
NonCopyable bar() { return a; }(除非启用 RVO 或移动) - STL 容器存储该类型(如
std::vector<noncopyable></noncopyable>)→ 插入、扩容等操作失败
禁用拷贝但允许移动的注意事项
禁用拷贝不等于禁止所有资源转移——移动构造函数和移动赋值运算符仍可显式定义。这是实现 RAII 类型(如 std::unique_ptr)的基础。
性能与兼容性影响:
- 移动操作必须显式声明为
= default或自定义,否则默认也不会生成(因为拷贝被删,编译器不再自动生成移动函数) - 若只禁用拷贝却不提供移动,类型变成“不可复制也不可移动”,几乎无法用于泛型容器或算法
- 注意移动后源对象状态:标准不要求置空,但应保证可析构、可赋值,否则使用者难以推理
真正容易被忽略的是:禁用拷贝后,是否同步检查了所有依赖它的接口(比如工厂函数返回方式、是否被误用于 std::shared_ptr 的构造参数等)。一个没注意到的隐式拷贝,就足以让整个模块编译不过。


















