C++ C++;:在抛出/捕获异常时,异常对象何时被销毁?

C++ C++;:在抛出/捕获异常时,异常对象何时被销毁?,c++,exception,destructor,C++,Exception,Destructor,我偶然发现了这篇文章:并对异常对象何时被破坏感到好奇 这里是我的异常结构,正如本文中的第二个示例(使用一些std::cout) 问题1:如果在第二次捕获之前,基础_EX被破坏,它是如何捕获的 问题2:为什么破坏要比建造多 问题3:当我将两个捕获更改为按值捕获而不是按常量捕获时,为什么输出会变为 constructing BASE_EX 1 constructing DERIVED_EX 1 First catch block: BASE_EX 1 destructing BASE_EX 1 de

我偶然发现了这篇文章:并对异常对象何时被破坏感到好奇

这里是我的异常结构,正如本文中的第二个示例(使用一些std::cout)

问题1:如果在第二次捕获之前,基础_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如何在引擎盖下工作的推荐阅读都将非常好。谢谢。

了解它们何时被销毁的最好方法是假装捕获是一个函数:

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

只要这个伪函数参数被销毁,异常对象就会被销毁,如果在这里输入异常处理程序就像它是一个普通函数调用一样。

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

第一个
throw
将异常对象设置为
DERIVED\u ex0
。内部
catch
获取对该异常对象的
BASE\u ex0
基类子对象的引用。由于
what
virtual
,调用它会导致
派生的\u EX
报告其类型。但是,当您再次抛出ex时,
ex
仅具有静态类型
BASE\u-ex
,因此新异常对象被选择为
BASE\u-ex
,它是通过仅复制第一个异常对象的
BASE\u-ex
部分创建的。当我们退出第一个
catch
时,第一个异常对象被销毁,外部catch接收新的
BASE\u EX
对象。因为它实际上是一个
BASE\u-EX
,而不是一个
派生的\u-EX
,调用
什么反映了这一点。如果您通过值使两个
catch
s都成为

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
重新显示异常,可以避免复制问题和切片问题

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;
    }
}
异常对象在第一个
catch
结束时不会被销毁,因为它以
throw
退出,这表示要使用相同的异常对象来匹配更多的
catch
子句。它不会被复制到一个新的异常对象中并像
throw ex
所调用的那样被销毁


有关规则的详细说明,请参阅。

异常在其通过的每个块的末尾被概念上销毁,并传递一个副本。然而,编译器可能(并且在实践中,为了提高效率,通常会这样做)省略一些副本,但重要的是,不一定是所有副本。如果异常最终被捕获,它将在相关捕获处理程序的末尾停止存在。如果未捕获异常,则程序将通过调用
std:abort()
结束,并且不必调用析构函数。如果您跟踪复制(或移动?)构造函数的调用,您可能会更好地了解正在发生的事情。“如果[object]在[X]之前被破坏,它[仍然存在]?”——当人们试图跟踪构造和破坏,但忽略了该过程时,经常会出现这种形式的问题。让我们看看。。。是的,这里就是这样。这能回答你的问题吗?谢谢你们@Peter和JaMiT。我认为JaMiT共享的两个链接回答了我的问题。
void exception_handler(BASE_EX const& ex) {
    std::cout << "Second catch block: " << ex.what() << std::endl;
}
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
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
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