Try / Catch block substituted for a method block in a destructor
I was recently tasked with hunting down a memory leak in a part of our code. The leak ended up being in the destructor for a particular object...and I found something really strange. A former coworker wrote this:
File::~File()
try
{
Clear();
}
catch (...)
{
Log("caught exception");
}
The file class inherits from some base classes. My first question is: is this strictly legal C++? It compiles in Visual Studio 2008, but I showed it to a few friends / coworkers and they were fairly horrified that it worked.
It doesn't actually work as intended, though: the base class that this object inherits from has a destructor that is now never called (as opposed to if you just wrapped the destruct开发者_JS百科or in a regular method block, having the try / catch as part of that method).
Can anyone take a stab at explaining why this is allowed, and why the base class destructor was not called? The destructor here was not throwing.
This is a function try block and it's completely legal.
See, for example, here.
The only time that you can do something in a function try block that you can't do in a normal try block in a function is catch exceptions thrown by expression in a constructor initializer list (and even then you end up having to throw something), but that doesn't apply here.
This GOTW #66 is particularly interesting, although it concentrates more on constructors. It contains this "moral":
Since destructors should never emit an exception, destructor function-try-blocks have no practical use at all.
Just to add clarification, the code as written will cause any exception caught to be rethrown due to ISO/IEC 14882:2003 15.3 [except.handle] / 16:
The exception being handled is rethrown if control reaches the end of a handler of the function-try-block of a constructor or destructor. [...]
However it is legal to have a parameterless return
in the handler of a function try block for a destructor - it is only forbidden in a function try block for a constructor - and this will supress the rethrow of the exception. So either of these alternatives would prevent the exception from leaving the destructor.
File::~File()
try
{
Clear();
}
catch (...)
{
Log("caught exception");
return;
}
File::~File()
{
try
{
Clear();
}
catch (...)
{
Log("caught exception");
}
}
To answer the second part, "why the base class destructor was not called?", 12.4/6:
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct members, the destructors for X’s direct base classes... A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called.
This doesn't say that the member and base destructors are called if the destructor throws. However, 15.2/2 says:
An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects,
I think this should be true whether the object is "partially destroyed" because of an exception thrown from the body of the destructor, or because of an exception thrown from the the function try block of the destructor. I'm pretty sure that "after the body of the destructor" is supposed to mean also after a function try block.
Apparently Microsoft disagrees, though, and because of the function try block it hasn't generated "the body of the destructor", and hasn't done the things that happen after executing "the body of the destructor".
That doesn't sound right to me. GCC 4.3.4 does execute the base class destructor, whether the derived class dtor function try block throws or not. In the case where it throws, the base is destructed before the catch clause is executed.
精彩评论