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对象:
- 第一次析构:来自try块里的局部对象
obj。当执行throw obj;时,程序会触发栈展开——销毁try块作用域内的所有局部对象,所以obj会被立即析构,对应输出里的第一个Box destructor is executed。 - 第二次析构:来自catch块里的
msg。这里用的是值捕获,编译器会把抛出的异常对象复制一份到msg中。当catch块执行完毕,msg的生命周期结束,触发第二次析构。 - 第三次析构:来自编译器创建的异常临时对象。
throw obj;时,编译器会复制obj生成一个专门的异常对象(存储在C++运行时管理的特殊内存区域,不是普通栈空间)。当整个异常处理流程完成后,这个临时异常对象会被销毁,触发第三次析构。
简单说:三个对象分别是try里的局部对象、throw时的异常临时对象、catch里的捕获副本,所以对应三次析构调用。
内容的提问来源于stack exchange,提问作者juztcode
相关产品推荐
相关产品推荐

