实参抛出异常时,C++函数形参yy的析构函数为何未调用?
问题:C++函数形参析构未调用的原因分析
示例代码
#include <iostream> using namespace std; class X {}; class Y { public: Y() { cout << "Y ctor\n"; } ~Y() { cout << "Y dtor\n"; } }; int gun() { throw X{}; return 42; } void fun(Y yy, int i) {} int main() { Y a; cout << "--------\n"; try { fun(a, gun()); } catch (const X&) { cout << "catched\n"; } cout << "--------\n"; }
运行输出
Y ctor -------- catched -------- Y dtor
疑问
为何由实参a拷贝初始化的形参yy的析构函数未被调用?即使调换函数参数顺序(如定义void fun(int i, Y yy)并调用fun(gun(), a)),输出结果仍一致,因此并非函数实参求值顺序的问题?
解答
核心原因:形参yy未被构造
你的代码中看不到yy的析构输出,本质是**yy根本没有被构造**——你的编译器始终选择了先执行会抛出异常的gun(),导致yy的初始化步骤从未启动。
基于C++标准的详细解释
根据C++标准对函数调用的规则:
- 函数名的求值会先于所有实参表达式的计算。
- 不同实参的求值、对应形参的初始化之间是不确定顺序的(编译器可自由选择执行顺序,这是标准允许的优化空间)。
- 若在实参求值过程中抛出异常,只有已经完成初始化的形参会被销毁(调用析构);未开始初始化的形参,自然不会触发析构逻辑。
在你的测试场景中:
- 无论函数参数顺序如何,编译器都优先选择了先求值
gun()。gun()执行时直接抛出异常,此时yy对应的实参求值和形参初始化还未开始,因此yy从未被构造,也就不会有析构函数的输出。
验证方法
如果给Y类添加一个带输出的拷贝构造函数:
Y(const Y&) { cout << "Y copy ctor\n"; }
重新编译运行后,你可能会看到两种结果:
- 多数情况下仍输出原结果(编译器优先执行
gun(),无拷贝构造); - 少数编译器可能选择先初始化
yy再执行gun(),此时会依次输出Y copy ctor、Y dtor,之后才是catched。
这说明问题的核心确实是编译器对实参求值顺序的选择——你的测试环境中编译器通过优先执行抛出异常的逻辑,避免了不必要的拷贝操作。
内容的提问来源于stack exchange,提问作者CPPL
相关产品推荐
相关产品推荐

