关于C ++构造函数中的异常的困惑

时间:2019-05-06 19:59:13

标签: c++ exception

我发现有关在C ++中的构造函数中引发异常的矛盾答案。 this link中的答案之一表示,如果在构造函数内引发异常,则假定构造未完成,因此不会调用析构函数。但是this link使用在构造函数中创建并在析构函数中清除的互斥锁的示例讨论了RAII概念。它说,如果在构造函数中创建了一个互斥锁,然后构造函数调用了一个引发异常的函数,并且未定义任何异常处理程序,则析构函数仍将被调用,并且该互斥锁将被清除。什么 我想念吗?

2 个答案:

答案 0 :(得分:4)

正在构造的对象的析构函数不执行,但是其构造的所有成员都被分解;例如:

struct A {
   A(int x) { ... }
   ~A() { ... }
};

struct B {
   A a1, a2, a3;
   B() : a1(1), a2(2), a3(3) { ... }
   ~B() { ... }
};

如果在构建B实例时,a1的构建进展顺利,那么a2的构建进展顺利,但a3的构建引发了异常,那么会发生什么情况是a2将被销毁(调用~A),然后a1将被销毁,但不会调用~B,因为构造函数未完成(主体没有甚至无法开始)。

即使将异常抛出到...的{​​{1}}主体中,也将通过调用B()销毁所有A子对象,但仍然~A不会被调用。

仅当~B的构造函数已完成完成时,您得到的是真实的B实例,然后在销毁时调用B来执行销毁代码。

答案 1 :(得分:1)

让我们看一下这段代码:

#include <iostream>
#include <stdexcept>

class A {
public:
    A() {
        std::cout << "A's constructor\n";
    }

    ~A() {
        std::cout << "A's destructor\n";
    }
};

class B {
public:
    B() {
        std::cout << "B's constructor - begin\n";
        throw std::runtime_error("Error");
        std::cout << "B's constructor - end\n";
    }

    ~B() {
        std::cout << "B's destructor\n";
    }

private:
    A a;
};

int main() {
    try {
        B b;
    } catch(const std::runtime_error& exc) {
        std::cerr << exc.what() << '\n';
    }
}

这是程序的输出:

A's constructor
B's constructor - begin
A's destructor
Error

通常,在构造对象时,首先调用其fileds的构造函数,然后执行该对象的构造函数。对于每个成功执行的构造函数,必须有一个调用的析构函数。析构函数的调用顺序相反。如果构造函数失败,则不会调用任何析构函数,但如果在构造对象期间构造了其部分或全部字段,则它们将被销毁。

回到示例,在main函数中,我创建了一个类B的对象。该对象包含类A的成员。创建b对象时,首先将构造它的字段(在这种情况下,它是称为a的成员)-这是输出的第一行。然后b对象的构造函数开始执行(输出的第二行)。 B的构造函数引发异常。由于b的字段的构造函数(即a的构造函数)已成功执行,因此必须调用a的析构函数-输出的第三行。现在,b的构造函数尚未成功完成其执行,因此不会为b调用析构函数。输出的最后一行是main函数中异常处理代码的作用。

在此示例中,您可以看到,当构造函数成功执行后,一段时间后,将调用相应的析构函数。但是,如果构造函数失败,则不会调用该对象的析构函数。

成功执行的构造函数和析构函数始终配对。这是一条非常通用的规则,对于基类(如果有的话),数组中分配的对象,堆栈中分配的对象等也同样有效。即使是非常奇怪和复杂的情况也总是以这种方式处理。

相关问题