A vague translation to plain terms would be: function-try-blocks can only be used to translate exceptions and always use RAII and each resource should be managed by a single object, and they do not contradict. Oh, well, the translation is not exactly that, but the argument eventually leads to those two conclusions.
The second quote, from the C++FAQ lite states that the destructor will not be called for an object whose constructor did not complete. That in turn means that if your object is managing resources, and more so when it manages more than one, you are in deep trouble. You can catch the exception before it escapes the constructor, and then try to release the resources that you have acquired, but to do so you would need to know which resources were actually allocated.
The first quote says that a function try block within a constructor must throw (or rethrow), so the usefulness of it is very limited, and in particular the only useful thing that it can do is translate the exception. Note that the only reason for the function try block is to catch an exception during the execution of the initialization list.
But wait, is the function try block not a way to handle the first problem?
Well... not really. Consider a class that has two pointers and stores memory in them. And consider that the second call might fail, in which case we will need to release the first block. We could try to implement that in two different ways, with a function try block, or with a regular try block:
// regular try block // function try block
struct test {
type * p;
type * q;
test() : p(), q() { test() try : p( new int ), q( new int ) {
try {
p = new type;
q = new type;
} catch (...) { } catch (...) {
delete p; delete p;
throw;
} } // exception is rethrown here
}
~test() { delete p; delete q; }
};
We can analyze first the simple case: a regular try block. The constructor in the first initializes the two pointers to null (: p(), q()
in the initialization list) and then tries to create the memory for both objects. In one of the two new type
an exception is thrown and the catch block is entered. What new
failed? We do not care, if it was the second new that failed, then that delete
will actually release p
. If it was the first one, because the initialization list first set both pointers to 0
and it is safe to call delete
on a null pointer, the delete p
is a safe operation if the first new failed.
Now on the example on the right. We have moved the allocation of the resources to the initialization list, and thus we use a function try block, which is the only way of capturing the exception. Again, one of the news fail. If the second new failed, the delete p
will free the resource allocated in that pointer. But if it was the first new that failed, then p
has never been initialized, and the call to delete p
is undefined behavior.
Going back to my loose translation, if you used RAII and only one resource per object, we would have written the type as:
struct test {
std::auto_ptr<type> p,q;
test() : p( new type ), q( new type )
{}
};
In this modified example, because we are using RAII, we do not really care about the exception. If the first new
throws, no resource is acquired and nothing happens. If the second throw fails, then p
will be destroyed because p
has been fully constructed before the second new
is attempted (there is sequence point there), and the resource will be released.
So we do not even need a try
to manage the resources... which leaves us with the other usage that Sutter mentioned: translating the exception. While we must throw out of the failing constructor, we can choose what we throw. We might decide that we want to throw a custom made initialization_error
regardless of what was the internal failure in the construction. That is what the function try block can be used for:
struct test {
std::auto_ptr<type> p,q;
test() try : p( new type ), q( new type ) {
} catch ( ... ) {
throw initialization_error();
}
};