This post is a follow up to The Returning Function that Never Returned, which I wrote a couple of months ago. You can read it first if you want, it is only around a hundred words, but this post can be read on its own as well.
In my previous post, I presented a function with try
outside of the braces. This is called a “function try block”. The example went like this:
std::string foo() try { bar(); return "foo"; } catch (...) { log("Unable to foo!") }
This is completely useless, even dangerous. The try
should be moved inside the function itself, like this:
std::string foo() { try { bar(); return "foo"; } catch (...) { log("Unable to foo!") //either rethrow or throw something else } //or make sure something is returned }
So why were function try blocks introduced to the language in the first place? Have a look at this:
class A : public B{ A(int x) : c(x){} C c; }
How do you catch an exception thrown by C
s constructor? Having try
inside the function block is too late. Initializing c
inside the function block is not a solution either, since c
will always be initialized before the body of the constructor, even if it is not mentioned in the initializer list. The only way to catch such an exception is to use a function try block on the constructor, and that is also why they were introduced. (Note that this argument is also valid if B
s constructor might throw.)
This is also the only sane case in which to use them. The other to candidates are regular functions and destructors, both of which I will get back to in a moment.
First, let’s have a look at what you can do with a function try block on a constructor. When the catch block reaches its end, it will rethrow the exception. It is impossible to swallow it. If you could swallow the exception, the code that tried to construct an object of this type would have no way of knowing that construction failed. A failed construction means the object doesn’t exist, and it doesn’t make sense to continue pretending nothing has happened. The only thing you can do is to throw an exception of another type, or cause a side effect (such as logging) and then retrhow. In particular, you cannot try to recover from the problem.
The same goes for destructors, you cannot swallow the exception. And since you really should avoid having destructors throw, using function try blocks on destructors is a bad idea.
Try blocks on regular functions behave a bit differently; if the end of the catch block is reached, the function will automatically return. But if you have a non-void function, this doesn’t make sense at all, as I mentioned in my previous post.
So in conclusion:
- Only use function try blocks for constructors.
- Don’t try to do anything else than rethrowing (possibly another type) or cause side effects like logging.
For a more in-depth discussion of this, have a look at Sutter’s Mill: Constructor Failures (or, The Objects That Never Were) by Herb Sutter. If you would like a recap of exception handling and constructor initializers thrown in, I recommend to start with Introduction to Function Try Blocks by Alan Nash.
But for the destructors it makes sense to catch exceptions thrown. Since you shall not throw in your destructor.
So lets say that you have some library functions you are using and for some reason or the other the cleanup throws an exception. If you can catch this in your destructor and log it and then continue you have avoided the problem that throwing in the destructor may cause to the rest of your application. Or am I missing something here?
Tore
I agree, it does make sense to make every effort not to throw in a destructor. The thing is, rethrow on a destructor try block is not optional. Whether you type “throw” or not, some exception will be thrown. The only thing you can do to prevent the original exception from being thrown is to throw one of a different type.
The solution is to put your try-block inside the destructor body, and hope none of the base destructors throw.
For folks using older compilers, a templated constructor with a function try block will in general not compile with Microsoft Visual C++ (MSVC) 7.1. MSVC 9.0 appears to compile such code fine, but I have not tested that extensively. For some reason function try blocks just don’t appear in my code… :-)
7.1, that’s quite old, isn’t it? :)
Personally I am mostly using Sun Studio* and gcc at the moment, but I will start using Visual Studio this fall. I haven’t used VS since before it was even called Visual Studio, so it will be interesting to see what it has to offer these days!
* (Or Oracle Solaris Studio or whatever Oracle decided to call it)