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

C++ try/catch对象创建疑问:析构调用与输出顺序问题

C++异常抛出与析构的两个常见疑问

首先看你的代码:

#include <iostream> 
#include <string> 

class Box{ 
public: 
    std::string show(){ 
        std::cout<<"Box show executed"<<std::endl; 
        return "msg"; 
    }; 
    ~Box(){ 
        std::cout<<"Box destructor is executed"<<std::endl; 
    }; 
}; 

int main(){ 
    try{ 
        Box obj; 
        std::cout<<"Coming here"<<std::endl; 
        throw obj; 
    } catch(Box msg){ 
        std::cout<<"I have caught the exception: \n"<<msg.show()<<std::endl; 
    } 
}

在GCC下的输出:

Coming here
Box destructor is executed
Box show executed
I have caught the exception: 
msg
Box destructor is executed
Box destructor is executed

疑问1:为什么“Box show executed”在“I have caught the exception:”之前打印?

这是表达式求值顺序导致的典型问题。看catch块里的这条链式输出语句:

std::cout<<"I have caught the exception: \n"<<msg.show()<<std::endl;

C中,链式调用<<时,虽然运算符本身是从左到右执行,但每个操作数的求值顺序在C17之前是未指定的——不过这里有个不可绕过的逻辑:msg.show()的返回值是std::string,它必须先被计算出来,才能作为<<的右操作数传递给输出流。而show()函数内部自带std::cout输出操作,这个副作用会在show()被调用的瞬间立即执行,也就是在catch块里的前缀字符串"I have caught the exception: \n"被输出之前,show()已经完成了它的打印。

如果想让顺序符合预期,只要把语句拆成两行即可:

std::cout<<"I have caught the exception: \n";
std::cout<<msg.show()<<std::endl;

这样就能保证先打印前缀,再调用show()输出内容。


疑问2:为什么会调用三次析构函数?

你看到的三次析构,对应三个完全不同的Box对象:

  1. 第一次析构:来自try块里的局部对象obj。当执行throw obj;时,程序会触发栈展开——销毁try块作用域内的所有局部对象,所以obj会被立即析构,对应输出里的第一个Box destructor is executed。
  2. 第二次析构:来自catch块里的msg。这里用的是值捕获,编译器会把抛出的异常对象复制一份到msg中。当catch块执行完毕,msg的生命周期结束,触发第二次析构。
  3. 第三次析构:来自编译器创建的异常临时对象。throw obj;时,编译器会复制obj生成一个专门的异常对象(存储在C++运行时管理的特殊内存区域,不是普通栈空间)。当整个异常处理流程完成后,这个临时异常对象会被销毁,触发第三次析构。

简单说:三个对象分别是try里的局部对象、throw时的异常临时对象、catch里的捕获副本,所以对应三次析构调用。


内容的提问来源于stack exchange,提问作者juztcode

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:29:11