c++:在抛出/捕获异常时,异常对象何时被析构?



我看到了这篇文章:最佳实践:按值抛出,按const引用捕获,并对异常对象何时被破坏感到好奇。

下面是我的异常结构,就像文章中的第二个例子一样(有一些std::cout)。

struct BASE_EX {
static int id;
BASE_EX() { 
++id; 
std::cout << "constructing BASE_EX " << id << std::endl; 
}
virtual std::string const what() const { return "BASE_EX " + std::to_string(id); }
~BASE_EX() { std::cout << "destructing BASE_EX " << id << std::endl; }
};
struct DERIVED_EX : BASE_EX {
static int derived_id;
DERIVED_EX() {
++derived_id; 
std::cout << "constructing DERIVED_EX " << derived_id << std::endl; 
}
std::string const what() const { return "DERIVED_EX " + std::to_string(derived_id); }
~DERIVED_EX() { std::cout << "destructing DERIVED_EX " << derived_id << std::endl; }
};
int BASE_EX::id = 0;
int DERIVED_EX::derived_id = 0;

运行main函数时,通过const&:

进行捕获
int main() {
try {
try {
throw DERIVED_EX();
} catch(BASE_EX const& ex) {
std::cout << "First catch block: " << ex.what() << std::endl;
throw ex;
}
} catch(BASE_EX const& ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
}

我得到

constructing BASE_EX 1
constructing DERIVED_EX 1
First catch block: DERIVED_EX 1
destructing DERIVED_EX 1
destructing BASE_EX 1
Second catch block: BASE_EX 1
destructing BASE_EX 1

问题1:如果BASE_EX在第二次捕获之前被析构,它是如何被捕获的?

问题2:为什么析构比构造多一个?

问题3:当我将两个捕获都更改为按值捕获而不是按常量捕获时,为什么输出变成了

constructing BASE_EX 1
constructing DERIVED_EX 1
First catch block: BASE_EX 1
destructing BASE_EX 1
destructing DERIVED_EX 1
destructing BASE_EX 1
Second catch block: BASE_EX 1
destructing BASE_EX 1
destructing BASE_EX 1

任何关于cpp try-catch如何在引擎盖下工作的推荐读物都会很棒。谢谢。

如果要标记对象的构造函数,请标记所有的;)

struct BASE_EX {
static int count;
int id;
BASE_EX() : id(count++) { 
std::cout << "constructing BASE_EX " << id << std::endl; // usually std::endl is unnecessary (it's just "n" followed by std::flush), but since we're playing with crashes it's probably a good idea
}
BASE_EX(BASE_EX const &other) : id(count++) {
std::cout << "copying BASE_EX " << other.id << " as BASE_EX " << id << std::endl;
}
// implicit move constructor not declared
virtual std::string what() const { return "BASE_EX " + std::to_string(id); } // marking by-value return as const does absolutely nothing
~BASE_EX() { std::cout << "destructing BASE_EX " << id << std::endl; } // reminder that base class destructors should generally be virtual; not required in this case
};
int BASE_EX::count = 0;
struct DERIVED_EX : BASE_EX {
static int count;
int id;
DERIVED_EX() : BASE_EX(), id(count++) {
std::cout << "constructing DERIVED_EX " << id << std::endl; 
}
DERIVED_EX(DERIVED_EX const &other) : BASE_EX(other), id(count++) {
std::cout << "copying DERIVED_EX " << other.id << " as DERIVED_EX " << id << std::endl;
}
// implicit move constructor not declared
std::string what() const override { return "DERIVED_EX " + std::to_string(id); }
~DERIVED_EX() { std::cout << "destructing DERIVED_EX " << id << std::endl; }
};
int DERIVED_EX::count = 0;

constructing BASE_EX 0
constructing DERIVED_EX 0
First catch block: DERIVED_EX 0
copying BASE_EX 0 as BASE_EX 1
destructing DERIVED_EX 0
destructing BASE_EX 0
Second catch block: BASE_EX 1
destructing BASE_EX 1

第一个throw设置异常对象为DERIVED_EX 0。内部的catch获得对该异常对象的BASE_EX 0基类子对象的引用。由于whatvirtual,调用它会导致DERIVED_EX报告它的类型。但是,当您再次执行throw ex时,ex只有静态类型BASE_EX,因此新的异常对象被选择为BASE_EX,并且通过仅复制第一个异常对象的BASE_EX部分来创建它。当我们退出第一个catch时,第一个异常对象被销毁,外部catch接收到新的BASE_EX对象。由于确实是BASE_EX,而不是DERIVED_EX,调用what反映了这一点。如果将两个catch按值设置,则得到

constructing BASE_EX 0
constructing DERIVED_EX 0
copying BASE_EX 0 as BASE_EX 1
First catch block: BASE_EX 1
copying BASE_EX 1 as BASE_EX 2
destructing BASE_EX 1
destructing DERIVED_EX 0
destructing BASE_EX 0
copying BASE_EX 2 as BASE_EX 3
Second catch block: BASE_EX 3
destructing BASE_EX 3
destructing BASE_EX 2

按值catch时,复制异常对象以初始化catch形参。在执行这样的catch块期间,有两个对象表示异常:实际的异常对象(没有名称)和为catch块制作的异常副本(可以命名)。第一个拷贝是第一个异常对象到第一个catch参数的拷贝。第二个副本是该参数作为第二个异常对象的副本。第三个是将该异常对象复制到第二个catch的形参中。当我们进入第一个catch时,异常的DERIVED_EX部分已经被切掉了。根据通常的作用域规则,catch参数在每个catch结束时被销毁。当相应的catch块退出时,异常对象将被销毁。

通过不按值获取异常和不使用throw <catch-parameter>重新抛出异常,可以避免复制问题和切片问题。

int main() {
try {
try {
throw DERIVED_EX();
} catch(BASE_EX const &ex) {
std::cout << "First catch block: " << ex.what() << std::endl;
throw;
}
} catch(BASE_EX const &ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
}

constructing BASE_EX 0
constructing DERIVED_EX 0
First catch block: DERIVED_EX 0
Second catch block: DERIVED_EX 0
destructing DERIVED_EX 0
destructing BASE_EX 0

异常对象不会在第一个catch结束时被销毁,因为它与throw一起退出,这表明相同的异常对象将用于匹配更多的catch子句。它不会像throw ex那样被复制到一个新的异常对象中并销毁。

有关规则的详细描述,请参阅cppreference。

理解它们何时被销毁的最好方法是假装catch类是一个函数:

catch(BASE_EX const& ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}

做一个临时调整,假装这是一个函数,ex只是这个函数的一个参数:

void exception_handler(BASE_EX const& ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}

异常对象将在这个伪函数形参被销毁时被销毁,如果异常处理程序像普通函数调用一样输入,这里

最新更新