Need for try catch within a constructor
The link http://gotw.ca/gotw/066.htm states that
Moral #1: Constructor function-try-block handlers have only one purpose -- to translate an exception. (And maybe to do logging or some other side effects.) They are not useful for any other purpose.
While http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8
If a constructor throws an exception, the object's destructor is not run. If your object has already done something that needs to be undone (such as allocating som开发者_Python百科e memory, opening a file, or locking a semaphore), this "stuff that needs to be undone" must be remembered by a data member inside the object.
Are these 2 statements not contradictory? The first one kind of implies that the try catch within a constructor is pretty much useless while the second says that it is needed to free resources. What am i missing here?
Moral #1 talks about function-try-block & the second statement talks about a normal try catch block, both are distinctly different.
You need to understand the different between the two to understand how the two sentences make sense. This answer here explains that.
They refer to different things.
The first one is related to the function-try
block, i.e. a try
block that includes a whole function, and, in the case of constructors, includes also the calls to the constructors of the base classes and of the member objects, i.e. stuff that is executed before the actual constructor body is run.
The morale is that, if a base class or a class member fails to constructs correctly, the object construction must fail with an exception, because otherwise your newly constructed object is left in an inconsistent state, with the base object/member objects half constructed. So, the purpose of such try
block must be only to translate/rethrow such exception, maybe logging the incident. You can't do in any other way: if you don't explicitly throw
inside your catch
, an implicit throw;
is added by the compiler to prevent the "half-constructed object" eventuality.
The second one refers to exceptions that are risen inside the body of your constructor; in this case, it says that you should use a "regular" try
block to catch the exception, free the resources you have allocated up to now and then rethrow, because the destructor won't be called.
Notice that this behavior makes sense, since the implicit contract for the destructor, as for any other member function that is not the constructor, is that it expects to work on an object in a consistent state; but an exception thrown from the constructor means that the object hasn't been fully constructed yet, so this contract would be violated.
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();
}
};
Are these 2 statements not contradictory?
No. The second one basically means if the constructor throws an exception which comes out of it (i.e the constructor), then the destructor doesn't get called. And the first one means that function-try-block doesn't let the exception come out of the constructor. It catches it inside the constructor itself, and handles it right there.
First, a constructor function-try-block isn't the same as a try-catch within a constructor.
Second, saying "stuff that needs to be undone must be remembered by a data member" is not the same as saying, "use try-catch blocks to undo things".
There's no contradiction between the two statements, they're talking about different things. In fact, the second one isn't talking about try-catch at all.
精彩评论