C ++ does not prohibit the Destructor from throwing exceptions, but it does not encourage the following:
Class widget {
Public:
...
~ Widget (); // it is assumed that an exception may be thrown.
};
Void dosomething ()
{
STD: vector <widget> V;
...
}
When vector V is destroyed, it has the responsibility to destroy all its contained widgets. Assume that an exception is thrown during the first element of the destructor, and the others should be destroyed. Therefore, V should call each of these destructor. However, if the second widget destructor throws an exception. Now, there are two exceptions that work at the same time, which is too much for C ++. If both exceptions exist at the same time,ProgramIf the execution is not terminated, it will lead to unclear behaviors. Even if the container or arrays is not used, the program may end prematurely or be ambiguous. C ++ does not like destructor to throw an exception.
What if your destructor must execute an action that may throw an exception upon failure? For example, there is a class responsible for database connection:
Class dbconnection {
Public:
...
Static dbconnection create ();
Void close (); // closes online. If the connection fails, an exception is thrown.
};
In order for the customer not to 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 {
Public:
...
~ Dbconn ()
{
DB. Close ();
}
PRIVATE:
Dbconnection dB;
};
This allows the customer to writeCode:
{
Dbconn DBC (dbconnection: Create ());
...
} // After the block ends, the dbconn object is destroyed, and therefore close is automatically called for the dbconnection object.
If close is successfully called, everything will be fine, but if this call causes an exception, the dbconn destructor will spread the exception, that is, allow it to leave the destructor. This can cause problems, because it throws out uncontrollable troubles.
There are two ways to avoid this problem. The dbconn destructor can:
1. If close throws an exception, it ends. Generally, this is done by calling abort.
Dbconn ::~ Dbconn ()
{
Try {dB. Close ();}
Catch (...) {
Write down the call to close failed;
STD: Abort ();
}
}
If the program fails to be executed after it encounters an "error occurred during the Destructor", "Force end program" is a reasonable option. That is to say, calling abort can preemptively produce "ambiguous behaviors.
2. Swallow the exception caused by calling close.
Dbconn ::~ Dbconn ()
{
Try {dB. Close ();}
Catch (...) {
Write down the call to close failed;
}
}
In general, it is a bad idea to swallow an exception because it suppresses the important information of "some actions failed! However, sometimes "Swallowing exceptions" is better than "ending the program rashly" or "risks brought about by ambiguous behaviors. To make this a viable solution, the program must be able to proceed reliably even after an error is encountered and ignored.
These methods are not attractive. The problem is that neither of them can respond to the situation that "causes close to throw an exception.
A better strategy is to redesign the dbconn interface so that its customers have the opportunity to respond to possible problems. For example, dbconn can provide a close function by itself, giving the customer a chance to handle "exceptions caused by this operation ". Dbconn can also be used to check whether the managed dbconnection has been closed and disabled by its destructor without being closed. This prevents lost database connections. However, if the dbconn destructor fails to call close, we will return to the old path of "forced end program" or "Swallowing exception.
Class dbconn {
Public:
...
Void close ()
{
DB. Close ();
Closed = true;
}
~ Dbconn ()
{
If (! Closed ){
Try {dB. Close ();}
Catch (...) {Create a running record and write down the call to close;
...
}
}
}
PRIVATE:
Dbconnection dB;
Bool closed;
};
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. It is dangerous to throw an exception in the destructor, which may lead to the risk of "program premature termination" or "ambiguous behavior. The customer's own call to close does not burden them, but gives them a chance to handle errors. Otherwise, they have no chance to respond. If they do not think this opportunity is useful (maybe they believe there will be no 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, and the customer has no position to complain. After all, they had the opportunity to handle the problem first, but they chose to give up.
The Destructor must not throw an exception. If a function called by the Destructor may throw an exception, the Destructor should be able to catch any exception and swallow it (not spread)
Or terminate the program.
If the customer needs to respond to exceptions thrown during the running of an operation function, the class should provide a common function (not in the destructor) to execute this operation.