拷贝构造函数在复制初始化时调用,如MyClass b = a;或值传递参数;禁止拷贝应使用= delete显式删除,而非私有未定义函数,以实现编译期拦截。

拷贝构造函数会在对象被“复制初始化”时调用,不是所有赋值操作都会触发它;禁止拷贝的关键是让编译器无法生成或调用该函数,而不是靠运行时逻辑控制。
哪些写法会实际触发拷贝构造函数
触发的前提是:存在一个左值对象,且用于初始化另一个同类型对象(非引用、非指针),且未被编译器优化掉。常见场景包括:
-
MyClass a; MyClass b = a;—— 虽然语法像赋值,但这是初始化,调用拷贝构造 -
MyClass c(a);—— 直接初始化,明确调用拷贝构造 -
void func(MyClass x) { } func(a);—— 值传递参数,a拷贝进形参x -
MyClass create() { MyClass tmp; return tmp; } auto obj = create();—— 返回局部对象,可能调用(RVO 可能省略,但语义上仍属拷贝构造适用范围) -
std::vector<myclass> v; v.push_back(a);</myclass>—— C++11 前必拷贝;C++11 后若无移动构造,则退化为拷贝构造
注意:MyClass& r = a;、const MyClass& cr = a;、MyClass* p = &a; 都不调用拷贝构造;a = b; 调用的是拷贝赋值运算符,不是拷贝构造函数。
为什么有时候看不到拷贝构造函数被调用
不是代码没写对,而是编译器做了优化或语言规则发生了变化:
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- RVO/NRVO:函数返回局部对象时,编译器可能直接在调用方栈上构造对象,跳过拷贝构造调用(即使你写了,也不执行)
- 移动语义介入:如果类定义了移动构造函数,且传入的是右值(如
MyClass d = MyClass();),则优先调用移动构造,而非拷贝构造 - C++11 后隐式生成规则变化:一旦你声明了移动构造函数或移动赋值运算符,编译器就不再自动生成拷贝构造函数——此时若没显式定义,代码会编译失败
- 返回值类型是引用或指针:如
MyClass& func() { return a; },完全绕过拷贝
如何真正禁止拷贝(C++11 及以后)
用 = delete 显式删除,这是最干净、最明确的方式:
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};这样写之后:
-
NonCopyable a, b = a;→ 编译错误:use of deleted function -
void f(NonCopyable x) { }→ 传参时报错 -
return local_obj;(返回值类型为NonCopyable)→ 编译失败
不要用 C++98 风格的私有未定义函数——它只在链接期报错,且友元或成员函数内误用时难以排查;= delete 是编译期拦截,错误信息清晰,意图明确。
禁止拷贝时容易忽略的细节
禁止拷贝不是孤立操作,它会影响整个类的使用契约:
- 如果类管理独占资源(如文件句柄、socket、唯一指针),禁止拷贝合理;但如果还希望支持移动,必须同时定义移动构造和移动赋值,否则连
std::move都不能用 - 继承体系中,基类禁止拷贝,派生类默认也禁用——除非显式用
using Base::Base;或重新声明,但需谨慎,因为拷贝语义不可部分恢复 - STL 容器要求元素类型可拷贝(或可移动);若放入
std::vector<NonCopyable>,必须确保容器操作只用移动(如emplace_back)或提前预留空间避免扩容拷贝
真正难的不是写 = delete,而是判断“这个类是否真的不该被拷贝”——一旦接口暴露了拷贝能力,后续想收回,就是 ABI 破坏性变更。

















