本文介绍了如果构造函数抛出异常会发生什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

那我们会得到 UB 吗?

Will we get UB then?

我试过了:

#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;
}

AB 都没有调用析构函数.

The destructor was not called for both A and 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

所以任何时候构造函数在块范围变量的初始化过程中抛出,我们会得到 UB 吗?

So any time the constructor throws during initialization of block scope variables, do we get UB?

推荐答案

不,抛出异常是在对象构造期间发出错误信号的最佳方式.(由于没有返回值,除了构造一个无头对象之外别无他法,这在 C++ 中是不好的风格.)

No, throwing an exception is the best way to signal an error during object construction. (Since there's no return value, there's no other way, other than constructing a headless object, which is bad style in C++.)

来自这个人,Bjarne Stroustrup:http://www.stroustrup.com/bs_faq2.html#ctor-exceptions

From the man himself, Bjarne Stroustrup: http://www.stroustrup.com/bs_faq2.html#ctor-exceptions

(如果您在一个不允许异常的项目中工作,那么您必须使构造函数无误,并将任何可能失败的逻辑移动到有可能返回错误的工厂函数中.)

(If you are working in a project where exceptions aren't allowed, then you have to make the constructor infallible, and move any logic that could fail into a factory function that has the possibility of returning an error.)

回复:但是我的析构函数没有被调用"

Re: "But my destructor was not called"

确实如此.在 C++ 中,对象的生命周期从构造函数运行到完成时开始.它在调用析构函数时结束.如果 ctor 抛出,则不调用 dtor.

Indeed.In C++ the lifetime of an object is said to begin when the constructor runs to completion. And it ends right when the destructor is called. If the ctor throws, then the dtor is not called.

(但是在 this ctor 运行之前,其 ctors 已经运行完成的任何成员变量对象的 dtors 都会被调用.)

(But dtors of any member variable objects, whose ctors already ran to completion before this ctor ran, are called.)

您应该查阅标准或教科书以获取更多详细信息,尤其是.与涉及继承时发生的情况有关.作为一般经验法则,析构函数的调用顺序与构造相反.

You should consult the standard, or a good textbook for more details, esp. related to what happens when inheritance is involved. As a general rule of thumb, destructors are called in the reverse order of construction.

关于为什么是~B"的问题没有在您的特定代码中调用,这是因为您没有在 main 中捕获异常.如果您更改代码以便 main 捕获异常,则~B()";将被调用.但是,当抛出没有捕获的异常时,实现可以自由地终止程序,而无需调用析构函数或破坏静态初始化的对象.

Your question about why "~B" was not called in your specific code, it's because you do not catch the exception in main. If you change your code so that main catches the exception, then "~B()" will be called. But, when an exception is thrown which has no catch, the implementation is free to terminate the program without calling destructors or destroying statically initialized objects.

C++11 标准中的参考(重点是我的):

Reference in C++11 standard (emphasis mine):

15.5.1 std::terminate() 函数 [except.terminate]

1在某些情况下,必须放弃异常处理以使用不太微妙的错误处理技术.

1In some situations exception handling must be abandoned for less subtle error handling techniques.

...

2在这种情况下,调用 std::terminate() (18.8.3).在没有找到匹配处理程序的情况下,在调用 std::terminate() 之前是否解开堆栈是实现定义的.

2In 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 将不会被调用.异常处理很复杂,标准允许编译器编写者试验并选择他们认为在这方面最好的实现,但他们不能选择给出未定义的行为.

As a side note, generally speaking with gcc and clang, ~B will be called anyways in your example program, while with MSVC, ~B will not be called. Exception handling is complex and the standard allows that compiler writers can experiment with and choose what implementation that they think is best in this regard, but they cannot choose to give undefined behavior.

如果在这种情况下调用析构函数对您的程序非常重要,那么您应该确保在 main 中捕获异常,以便您的代码具有可移植性(在所有情况下都一样)符合的编译器).例如:

If it's really important for your program that the destructors are called even in this case, then you should make sure to catch exceptions in main so that your code will be portable (work the same on all conforming compilers). For example:

int main() {
    try {
        A a;
    } catch (...) {}
}

这样,像 MSVC 这样的编译器将有义务在退出之前调用 B 的析构函数.

This way, compilers like MSVC will be obligated to call the destructor of B before exiting.

这篇关于如果构造函数抛出异常会发生什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-06 22:48