You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何下述C++代码会两次调用拷贝构造函数与析构函数?

拷贝构造与析构函数被调用两次的原因解析

Hey Vijay,我来帮你拆解这个问题——你的代码里出现两次拷贝构造和三次析构(从输出能看到三次),其实完全是C++异常处理机制的正常行为,咱们一步步捋清楚:

执行流程逐行拆解

跟着代码的运行顺序,每一步对应输出内容:

  1. 进入fun()的try块,执行sam obj;:触发默认构造函数,输出第一行 Default Constuctor。
  2. 执行throw obj;:这里触发第一次拷贝构造。因为C++规定,抛出局部对象时,不能直接用栈上的原对象(栈展开过程中原对象会被销毁),必须把它拷贝到一块专门的「异常存储区」(独立于函数栈,生命周期持续到catch块处理完成),所以第一次调用拷贝构造,输出 Copy Constuctor。
  3. 进入catch(sam ex):这里是值捕获异常,意味着要把异常存储区里的对象再拷贝一份到catch块的局部变量ex中,所以第二次调用拷贝构造,输出第二行 Copy Constuctor。
  4. 执行catch块里的cout<<"\n fun ";,输出 fun 。
  5. 现在catch块执行完毕,开始销毁所有相关对象:
    • 首先销毁catch块里的ex:第一次调用析构,输出 destructor。
    • 然后销毁异常存储区的临时对象:第二次调用析构,输出 destructor。
    • 最后销毁try块里的obj(部分编译器会把这个销毁时机延迟到catch块结束后,属于栈展开的正常流程):第三次调用析构,输出 destructor。

验证逻辑的小技巧

你可以给每个对象加唯一标识,就能直观看到每个拷贝和析构对应的对象:

#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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:14:06