通过引用捕获(使用 catch)时异常对象的范围

Scope of Exception object when caught(using catch) by reference

本文关键字:异常 对象 范围 catch 使用 引用      更新时间:2023-10-16

我有3个问题:

1.总是说通过引用捕获异常对象。在下面的示例中,我看到析构函数是在执行 catch 块之前调用的,这意味着我们引用的是 catch 中的一个对象,当我们使用它时,该对象必须超出范围。那为什么要使用引用呢?

class MyException{
public:
    ~MyException()  {
        cout<<"Dtor for MyException called n";
    }
};
int main()
{
    try{
        MyException obj1;
        throw obj1;
    }
    catch(MyException& obj)
    {
        cout<<"Catched unhandled exception";
    }
}

2. 为什么在这里调用析构函数两次?一次在进入 catch 块之前,第二次在 catch 执行完成后

3. 通过引用捕获的抛出对象的生存期中的示例表明,析构函数只被调用一次,即只有在中定义了复制构造函数时,catch 块退出后。

a. 当我们通过引用捕获复制构造函数时,它的作用是什么?

b. 即使在定义了复制构造函数之后,它也不会被调用。那么它会产生什么影响呢?

c. 我也尝试在我的示例中定义一个复制构造函数,如下所示,但我仍然看到析构函数被调用了两次。为什么?:

MyException(const MyException& obj)
{
}

有人可以回答所有 5 个问题(第 3 个问题有 3 个部分(。

您正在抛出您作为本地临时创建的异常的副本。 所以有两个:第一个在你投掷时被摧毁,第二个(副本(在捕获块完成后被销毁。

试试这个:

try { throw MyException{}; }
catch (MyException const& obj) { }

编辑:如果你的复制构造函数真的没有在你的代码中被调用,那么我的猜测是编译器已经认识到异常类是空的? 编译器可以自由地执行它选择的任何优化,只要代码的行为是 - 就好像它没有一样。 例外是复制,但如果发生这种情况,您将不会得到双重析构函数调用。

  1. throw总是复制你扔的物体。您不能抛出不可复制的对象。事实上,如果throw无法为副本分配足够的内存,它甚至可能会失败。

    这应该解释为什么你看到一个析构函数在try块中被调用。它是您本地obj1的析构函数。一旦你扔出它的副本,它就会超出范围。

    至于通过引用捕获,它用于避免不必要的复制,更重要的是,潜在的切片。
  2. 再一次,它是您当地obj1的破坏者.当你扔掉它的副本时,它超出了范围。
  3. a( 如果通过引用捕获,则不执行复制。仅当您按值捕获时,才会执行复制(具有潜在的切片(。

    b( 你错了,throw调用一个复制构造函数来制作你的对象的副本并抛出它。不会调用复制构造函数的唯一情况是抛出临时对象并且编译器会忽略副本。

    c( 参考我的第一个和第二个答案。

他们建议您通过引用来获取 catch 块以避免切片(因为继承异常是标准的(并避免不必要的复制,因为throw无条件复制。

  1. 您无法通过引用接收obj1 throw obj1因为这会导致它被销毁(因为它不再在范围内(。因此,throw始终复制到您无法控制的临时内存位置。

  2. obj1和我之前提到的临时。 obj是对该临时的引用,则引用的临时将在 catch 块结束后和下一个操作之前销毁(类似于如果它实际上是 catch 块的本地(。

  3. 单个析构函数仅在省略副本时发生,标准不保证何时会发生,因为复制省略始终是可选的。定义复制构造函数没有区别,因为如果您不定义它(并且可以创建它(,编译器会为您定义它。删除复制构造函数只会导致throw是非法的。

    • 一个。如上所述,复制构造函数以复制到临时构造函数中。参考参考说是暂时的。

    • 二.你是否真的删除了复制构造函数? MyException(const MyException&) = delete;是你删除复制构造函数的方式,不定义它只会导致编译器为你创建一个。再说一遍,删除它会导致throw是非法的。

    • 三.由于未省略副本,并且存在两个对象,一个是临时对象,一个是你的对象。