Clause 08. do not escape exceptions from destructor. Clause 08. Escape functions.
Conclusion:
1. Do not spit out exceptions in destructor. If a function called by the Destructor may throw an exception, the Destructor should be able to catch any exception, then swallow them (not spread) or end the program.
2. If the customer needs to respond to exceptions thrown during the running of an operation function, the class should provide a common function (instead of in the destructor) to execute this operation.
C ++ is not prohibited, but does not encourage exceptions from destructor. Consider:
Class Widget {public :...~ Widget (){...} // suppose an exception may be thrown out here}; void doSomething () {std: vector <Widget> v ;...} // v is automatically destroyed here
When vector v is destructed, it has the responsibility to analyze all Widgets it contains. However, if two Widgets throws an exception during those calls, this is too much for C ++. If two exceptions exist at the same time, if the program is not executed, it will lead to unclear behavior. In this example, there will be an ambiguous behavior. The same situation will also occur if any other container (such as list, set) or TR1 of the standard library is used, or even array. C ++ does not like the destructor to spit out exceptions.
What if your destructor needs to execute an operation that may fail and throw an exception? Assume that a class is used for database connection. To ensure that the customer does not forget to call close () on the DBconnection object, a reasonable idea is to create a class for managing DBConnection resources, and call close in the Destructor:
Class DBConn {// This class is used to manage the DBConnection object public: // objects ...~ DBConn () // ensure that the database connection is always closed {db. close () ;}private: DBConnection db ;};
It allows users to program like this:
{// Open a block DBConn dbc (DBConnection: create (); // create a DBConnection and hand it over to the DBConn object for management... // use the DBConnection object through the DBConn interface} // At the block end point, the DBConn object is destroyed, and thus close is automatically called for the DBConnection object
As long as close is successfully called, everything is fine. However, if this call causes an exception, the DBConn destructor will spread the exception, that is, allow it to exit the destructor. This creates a problem because the Destructor throws a hot potato.
There are two main ways to avoid this problem.:
(1) Terminatethe program: Terminate the program if close throws an exception, generally by calling abort.
DBConn ::~ DBConn () {try {db. close () ;}catch (...) {create a running record and write down the call to close failed; std: abort ();}}
It has the advantage of preventing exceptions from spreading out of The Destructor (which will lead to ambiguous behavior ). That is to say, calling abort can predefine "ambiguous behavior.
(2) Swallowthe exception: Swallow the exception caused by calling close. In this example, the abort statement is removed in the first method.
Generally, it is a bad idea to swallow an exception because it hides important information about "some actions failed! However, in some cases, swallowing exceptions is more desirable than the risk of premature termination or unclear behavior of a program. The program must be able to run reliably after it encounters an error and ignores it. This can be a feasible choice.
The problem with the above method is that the two cannot respond to the situation that causes the close throw exception.
A better strategy isRedesign the DBConn InterfaceTo give the customer the opportunity to respond to possible problems.
Class DBConn {public:... void close () // The new function {db. close (); closed = true ;}~ DBConn () {if (! Closed) {try {db. close (); // close the connection (if the customer does not do so)} catch (...) {// If the close action fails, record and end the program or swallow the exception to create a running record, and write down the call to close ;...}}} private: DBConnection db; bool closed ;};
In this way, the responsibility for calling close is handed over from DBConn's destructor to DBConn's customers (a "double insurance call" is still included in DBConn's destructor "). If an operation may throw an exception when it fails, and another exception must be handled, the exception must be a function other than the self-destructor. This is because of destructor) exceptions are dangerous, and the risk of premature termination or unclear behavior of the program is always at risk. In this example, asking the customer to call close on their own does not impose a burden on them, but rather gives them a chance to handle errors. They can ignore it and rely on DBConn's destructor to call close. If an error occurs, close does throw an exception and DBConn swallowed the exception or ended the program. The customer has no position to complain. After all, they had the opportunity to handle the problem first, and they chose to give up.