大多数人说永远不要从析构函数抛出异常——这样做会导致未定义的行为。Stroustrup指出,“vector析构函数显式地为每个元素调用析构函数。这意味着如果元素析构函数抛出,则vector销毁失败…实际上没有很好的方法来防止析构函数抛出异常,因此标准库不保证元素析构函数是否抛出”(摘自附录E3.2)。
这篇文章似乎不是这么说的——抛出析构函数或多或少是可以的。
所以我的问题是,如果从析构函数抛出导致未定义的行为,你如何处理析构函数期间发生的错误?
如果在清理操作期间发生错误,您会忽略它吗?如果它是一个可以在堆栈中处理但不能在析构函数中处理的错误,那么从析构函数抛出异常难道没有意义吗?
显然,这种错误很少见,但也有可能发生。
我所在的小组认为,在析构函数中加入“作用域保护”模式在许多情况下都很有用——特别是对于单元测试。但是,要注意,在c++ 11中,抛出析构函数会导致调用std::terminate,因为析构函数隐式地用noexcept注释。
Andrzej krzemiezynski有一篇关于抛出析构函数的文章:
https://akrzemi1.wordpress.com/2011/09/21/destructors-that-throw/
他指出c++ 11有一种机制可以覆盖析构函数的默认noexcept:
In C++11, a destructor is implicitly specified as noexcept. Even if you add no specification and define your destructor like this:
class MyType {
public: ~MyType() { throw Exception(); } // ...
};
The compiler will still invisibly add specification noexcept to your destructor. And this means that the moment your destructor throws an exception, std::terminate will be called, even if there was no double-exception situation. If you are really determined to allow your destructors to throw, you will have to specify this explicitly; you have three options:
Explicitly specify your destructor as noexcept(false),
Inherit your class from another one that already specifies its destructor as noexcept(false).
Put a non-static data member in your class that already specifies its destructor as noexcept(false).
最后,如果您决定抛出析构函数,则应该始终注意双异常的风险(在堆栈因异常而被unwind时抛出)。这将导致调用std::terminate,这很少是您想要的。为了避免这种行为,你可以使用std::uncaught_exception()在抛出一个新的异常之前检查是否已经有一个异常。
我目前遵循的策略(很多人都这么说)是,类不应该主动从析构函数抛出异常,而是应该提供一个公共的“关闭”方法来执行可能失败的操作……
...but I do believe destructors for container-type classes, like a vector, should not mask exceptions thrown from classes they contain. In this case, I actually use a "free/close" method that calls itself recursively. Yes, I said recursively. There's a method to this madness. Exception propagation relies on there being a stack: If a single exception occurs, then both the remaining destructors will still run and the pending exception will propagate once the routine returns, which is great. If multiple exceptions occur, then (depending on the compiler) either that first exception will propagate or the program will terminate, which is okay. If so many exceptions occur that the recursion overflows the stack then something is seriously wrong, and someone's going to find out about it, which is also okay. Personally, I err on the side of errors blowing up rather than being hidden, secret, and insidious.
关键是容器保持中立,由所包含的类决定它们是否从析构函数抛出异常。
我所在的小组认为,在析构函数中加入“作用域保护”模式在许多情况下都很有用——特别是对于单元测试。但是,要注意,在c++ 11中,抛出析构函数会导致调用std::terminate,因为析构函数隐式地用noexcept注释。
Andrzej krzemiezynski有一篇关于抛出析构函数的文章:
https://akrzemi1.wordpress.com/2011/09/21/destructors-that-throw/
他指出c++ 11有一种机制可以覆盖析构函数的默认noexcept:
In C++11, a destructor is implicitly specified as noexcept. Even if you add no specification and define your destructor like this:
class MyType {
public: ~MyType() { throw Exception(); } // ...
};
The compiler will still invisibly add specification noexcept to your destructor. And this means that the moment your destructor throws an exception, std::terminate will be called, even if there was no double-exception situation. If you are really determined to allow your destructors to throw, you will have to specify this explicitly; you have three options:
Explicitly specify your destructor as noexcept(false),
Inherit your class from another one that already specifies its destructor as noexcept(false).
Put a non-static data member in your class that already specifies its destructor as noexcept(false).
最后,如果您决定抛出析构函数,则应该始终注意双异常的风险(在堆栈因异常而被unwind时抛出)。这将导致调用std::terminate,这很少是您想要的。为了避免这种行为,你可以使用std::uncaught_exception()在抛出一个新的异常之前检查是否已经有一个异常。
Throwing out of a destructor can result in a crash, because this destructor might be called as part of "Stack unwinding".
Stack unwinding is a procedure which takes place when an exception is thrown.
In this procedure, all the objects that were pushed into the stack since the "try" and until the exception was thrown, will be terminated -> their destructors will be called.
And during this procedure, another exception throw is not allowed, because it's not possible to handle two exceptions at a time, thus, this will provoke a call to abort(), the program will crash and the control will return to the OS.