那我们会得到 UB 吗?
我试过了:
#include <iostream>
struct B
{
B(){ std::cout << "B()" << std::endl; }
~B(){ std::cout << "~B()" << std::endl; }
};
struct A
{
B b;
A(){ std::cout << "A()" << std::endl; throw std::exception(); }
~A(){ std::cout << "~A()" << std::endl; }
};
int main()
{
A a;
}
A
和 B
都没有调用析构函数。
实际输出:
B()
A()
terminate called after throwing an instance of 'std::exception'
what(): std::exception
bash: line 7: 21835 Aborted (core dumped) ./a.out
http://coliru.stacked-crooked.com/a/9658b14c73253700
所以任何时候构造函数在 block 范围变量的初始化过程中抛出,我们会得到 UB 吗?
最佳答案
不,抛出异常是在对象构造期间发出错误信号的最佳方式。 (由于没有返回值,除了构造一个 headless 对象之外别无他法,这在 C++ 中是不好的风格。)
来自他本人,Bjarne Stroustrup:http://www.stroustrup.com/bs_faq2.html#ctor-exceptions
(如果您在一个不允许异常的项目中工作,那么您必须使构造函数无误,并将任何可能失败的逻辑移动到有可能返回错误的工厂函数中。)
Re:“但是我的析构函数没有被调用”
确实如此。 在 C++ 中,对象的生命周期从构造函数运行到完成时开始。它在调用析构函数时结束。如果 ctor 抛出,则不调用 dtor。
(但是在 this ctor 运行之前,其 ctors 已经运行完成的任何成员变量对象的 dtors 都会被调用。)
您应该查阅标准或好教科书以获取更多详细信息,尤其是。与涉及继承时发生的情况有关。作为一般经验法则,析构函数的调用顺序与构造相反。
您的问题是为什么在您的特定代码中没有调用“~B”,这是因为您没有在 main.c 中捕获异常。如果您更改代码以便 main 捕获异常,则将调用“~B()”。但是,当抛出没有捕获的异常时,实现可以自由地终止程序,而无需调用析构函数或破坏静态初始化的对象。
C++11 标准中的引用(重点是我的):
15.5.1 The std::terminate() function [except.terminate]
1 In some situations exception handling must be abandoned for less subtle error handling techniques.
...
2 In such cases, std::terminate() is called (18.8.3). In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called.
附带说明,一般来说,对于 gcc 和 clang,~B
无论如何都会在您的示例程序中被调用,而对于 MSVC,~B
将不会被调用.异常处理很复杂,标准允许编译器编写者试验并选择他们认为在这方面最好的实现,但他们不能选择给出未定义的行为。
如果在这种情况下调用析构函数对您的程序非常重要,那么您应该确保在 main
中捕获异常,以便您的代码具有可移植性(在所有情况下都一样)符合的编译器)。例如:
int main() {
try {
A a;
} catch (...) {}
}
这样,像 MSVC 这样的编译器将有义务在退出之前调用 B
的析构函数。
关于c++ - 如果构造函数抛出异常会发生什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32323406/