为何下述C++代码会两次调用拷贝构造函数与析构函数?
拷贝构造与析构函数被调用两次的原因解析
Hey Vijay,我来帮你拆解这个问题——你的代码里出现两次拷贝构造和三次析构(从输出能看到三次),其实完全是C++异常处理机制的正常行为,咱们一步步捋清楚:
执行流程逐行拆解
跟着代码的运行顺序,每一步对应输出内容:
- 进入
fun()的try块,执行sam obj;:触发默认构造函数,输出第一行Default Constuctor。 - 执行
throw obj;:这里触发第一次拷贝构造。因为C++规定,抛出局部对象时,不能直接用栈上的原对象(栈展开过程中原对象会被销毁),必须把它拷贝到一块专门的「异常存储区」(独立于函数栈,生命周期持续到catch块处理完成),所以第一次调用拷贝构造,输出Copy Constuctor。 - 进入
catch(sam ex):这里是值捕获异常,意味着要把异常存储区里的对象再拷贝一份到catch块的局部变量ex中,所以第二次调用拷贝构造,输出第二行Copy Constuctor。 - 执行catch块里的
cout<<"\n fun ";,输出fun。 - 现在catch块执行完毕,开始销毁所有相关对象:
- 首先销毁catch块里的
ex:第一次调用析构,输出destructor。 - 然后销毁异常存储区的临时对象:第二次调用析构,输出
destructor。 - 最后销毁try块里的
obj(部分编译器会把这个销毁时机延迟到catch块结束后,属于栈展开的正常流程):第三次调用析构,输出destructor。
- 首先销毁catch块里的
验证逻辑的小技巧
你可以给每个对象加唯一标识,就能直观看到每个拷贝和析构对应的对象:
#include <iostream> #include <exception> using namespace std; class sam { public: static int id_counter; int id; sam() : id(++id_counter) { cout<<"\n Default Constuctor, id: "<<id; } sam(int a) : id(++id_counter) { cout<<"\n Parameterised Constuctor, id: "<<id; } sam(const sam &obj) : id(++id_counter) { cout<<"\n Copy Constuctor, from id: "<<obj.id<<" to id: "<<id; } sam & operator = (const sam &obj) { cout<<"\n Overloaded assignment operator, from id: "<<obj.id<<" to id: "<<id; return *this; } ~sam() { cout<<"\n destructor, id: "<<id; } }; int sam::id_counter = 0; void fun() { try { sam obj; throw obj; } catch(char *ptr) { cout<<"\n Catch block"; } catch(sam ex) { cout<<"\n fun "; } } int main() { fun(); cout<<endl; return 0; }
运行这段代码,你会清晰看到每个拷贝的来源对象和每个析构的对象ID,整个生命周期一目了然。
优化方案:减少不必要的拷贝
如果你想避免第二次拷贝,只需要把catch的参数改成引用类型:catch(sam &ex)。这样catch块会直接绑定到异常存储区的对象上,不需要拷贝,此时拷贝构造只会被调用一次,析构也会变成两次(对应obj和异常存储区的临时对象)。
内容的提问来源于stack exchange,提问作者Vijay Nerkar
相关产品推荐
相关产品推荐

