我发现有关在C ++中的构造函数中引发异常的矛盾答案。 this link中的答案之一表示,如果在构造函数内引发异常,则假定构造未完成,因此不会调用析构函数。但是this link使用在构造函数中创建并在析构函数中清除的互斥锁的示例讨论了RAII概念。它说,如果在构造函数中创建了一个互斥锁,然后构造函数调用了一个引发异常的函数,并且未定义任何异常处理程序,则析构函数仍将被调用,并且该互斥锁将被清除。什么 我想念吗?
答案 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
函数中异常处理代码的作用。
在此示例中,您可以看到,当构造函数成功执行后,一段时间后,将调用相应的析构函数。但是,如果构造函数失败,则不会调用该对象的析构函数。
成功执行的构造函数和析构函数始终配对。这是一条非常通用的规则,对于基类(如果有的话),数组中分配的对象,堆栈中分配的对象等也同样有效。即使是非常奇怪和复杂的情况也总是以这种方式处理。