拷贝构造函数仅在四种明确时机被调用:①显式或复制初始化新对象(如A a(b)或A a = b);②值传递参数;③返回局部对象(未被RVO优化时);④抛出或按值捕获异常。
不是所有赋值都会触发拷贝构造函数。它只在「创建新对象」且「用同类型已有对象初始化」时才调用,本质是构造行为,不是赋值行为。
常见触发场景有且仅有以下四种:
A obj2(obj1); 或 A obj2 = obj1;(注意:后者是语法糖,仍调用拷贝构造,不是 operator=)void func(A a) { ... },调用时 func(obj) 会以 obj 构造形参 a
A func() { A tmp; return tmp; },返回时需用 tmp 构造临时返回对象throw A{}; 后被 catch(A e) 捕获)这是 C++ 的定义问题:A a = b; 是「复制初始化」(copy initialization),语义上等价于 A a(b);,属于构造而非赋值。即使重载了 operator=,这里也不会调用它。
容易混淆的点:
A a = b; → 调用拷贝构造函数A a; a = b; → 先调用默认构造,再调用 operator=
A a(b); → 直接调用拷贝构造函数(最明确写法)编译器可能对 A a = b; 执行 RVO/NRVO 优化,跳过拷贝构造——但这是优化结果,不是语言规则改变。
理解「不调用」比「调用」更能避免误判。以下看似相似,实则绕过拷贝构造:
void f(const A& a),f(obj) 不构造新对象,不调用任何构造函数A a = std::move(b); 或返回临时对象时,优先调用移动构造而非拷贝构造
最可靠方式是定义带日志的拷贝构造函数,并关闭优化验证:
struct A {
A() { std::cout << "default\n"; }
A(const A&) { std::cout << "copy ctor\n"; } // 显式定义
A(A&&) { std::cout << "move ctor\n"; }
};
然后用 g++ -O0 编译运行,观察输出。注意:
-O2 可能消除拷贝(RVO、copy elision),导致日志不出现,不代表逻辑不存在= delete),而代码中意外触发拷贝构造(如传值参数),错误信息会明确提示 use of deleted function 'A::A(const A&)'
真正难排查的是隐式触发 + 优化共存的情况,比如模板函数里传值参数,在不同实例化路径下可能有的走拷贝、有的被优化掉。这时候得结合编译器输出(-fno-elide-constructors 强制禁用 elision)和 AST 查看实际调用链。