An ambiguous translation into simple terms would be: function-try-blocks can only be used to translate exceptions and always use RAII, and each resource must be managed by one object, and they do not contradict. Oh, well, the translation is not quite the same, but the argument ultimately leads to these two conclusions.
The second quote from the C ++ FAQ lite list indicates that the destructor will not be called for an object whose constructor did not complete. This, in turn, means that if your object manages resources, and even more so when it manages several, you have serious problems. You can catch the exception before it escapes the constructor, and then try to free the resources that you acquired, but for this you will need to know which resources were actually allocated.
The first quote says that the try block function inside the constructor should throw (or reverse), so its usefulness is very limited, and in particular, the only useful thing that it can do is to throw an exception. Note that the only reason for the function's try block is an exception during the execution of the initialization list.
But wait, the function is not trying to handle the first problem?
Well, not quite. Consider a class that has two pointers and stores memory in them. And think that the second call may fail, in which case we will need to free the first block. We could try to implement this in two different ways: with a try block or a 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; } };
First, we can analyze a simple case: a regular try block. The constructor in the first initializes two pointers to zero ( : p(), q() in the initialization list), and then tries to create memory for both objects. In one of the two new type an exception is thrown and a catch block is introduced. What new failed? We donāt care if this was the second thing that failed, then delete really release p . If it was the first one, because in the initialization list both pointers are set to 0 , and it is safe to call delete on the null pointer, delete p is a safe operation if the first new one fails.
Now an example on the right. We have moved the allocation of resources to the initialization list, and therefore we use the try try block, which is the only way to catch the exception. Again, one of the news fails. If the second new failed, delete p free the resource allocated by this pointer. But if this was the first new one that failed, then p never initialized, and the delete p call was undefined.
Returning to my free translation, if you used RAII and only one resource for each object, we would write the type as:
struct test { std::auto_ptr<type> p,q; test() : p( new type ), q( new type ) {} };
In this modified example, since we are using RAII, we really don't care about the exception. If the first new throws, the resource does not receive and nothing happens. If the second throw fails, then p will be destroyed because p was completely created before the second new attempt is made (there is a sequence point), and the resource will be freed.
So we donāt even need to try to manage the resources ... which leaves us with another use that Sutter mentioned: throwing an exception. While we should throw away the unsuccessful constructor, we can choose what we throw. We can decide that we want to throw a custom initialization_error no matter what the internal design failure was. This is what a function block try can be used for:
struct test { std::auto_ptr<type> p,q; test() try : p( new type ), q( new type ) { } catch ( ... ) { throw initialization_error(); } };