拷贝构造函数仅在四种明确时机被调用:①显式或复制初始化新对象(如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)捕获)
为什么 A a = b; 看似赋值却调用拷贝构造?
这是 C++ 的定义问题:A a = b; 是「复制初始化」(copy initialization),语义上等价于 A a(b);,属于构造而非赋值。即使重载了 operator=,这里也不会调用它。
容易混淆的点:
立即学习“C++免费学习笔记(深入)”;
-
A a = b;→ 调用拷贝构造函数 -
A a; a = b;→ 先调用默认构造,再调用operator= -
A a(b);→ 直接调用拷贝构造函数(最明确写法)
编译器可能对 A a = b; 执行 RVO/NRVO 优化,跳过拷贝构造——但这是优化结果,不是语言规则改变。
哪些情况「不」调用拷贝构造函数?
理解「不调用」比「调用」更能避免误判。以下看似相似,实则绕过拷贝构造:
- 传引用参数:
void f(const A& a),f(obj)不构造新对象,不调用任何构造函数 - 返回局部对象但启用 RVO(Return Value Optimization):现代编译器(g++/clang 默认开启)常直接在调用方栈上构造返回对象,跳过拷贝
- 使用移动语义(C++11+):当源对象是右值且类定义了移动构造函数,
A a = std::move(b);或返回临时对象时,优先调用移动构造而非拷贝构造 - 聚合类型(如 plain struct)的 POD 成员复制:若类无用户定义拷贝构造,且满足 trivially copyable 条件,底层可能只是 memcpy,但语言层面仍视为“调用隐式生成的拷贝构造”——这点容易引发调试困惑,需看编译器实际行为
调试时怎么确认拷贝构造是否真的被调用?
最可靠方式是定义带日志的拷贝构造函数,并关闭优化验证:
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),导致日志不出现,不代表逻辑不存在 - C++17 起,某些 copy elision 变成强制行为(如函数返回临时对象),此时拷贝构造即使存在也禁止调用——但函数仍必须可访问、可调用(否则编译失败)
- 若类禁用了拷贝(
= delete),而代码中意外触发拷贝构造(如传值参数),错误信息会明确提示use of deleted function 'A::A(const A&)'
真正难排查的是隐式触发 + 优化共存的情况,比如模板函数里传值参数,在不同实例化路径下可能有的走拷贝、有的被优化掉。这时候得结合编译器输出(-fno-elide-constructors 强制禁用 elision)和 AST 查看实际调用链。










