Need for try catch inside constructor

Link http://gotw.ca/gotw/066.htm states that

Moral # 1: The constructor's try-block handlers have only one purpose - to throw an exception. (And perhaps logging or some other side effects.) They are not useful for any other purpose.

Bye http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8

If the constructor throws an exception, the object's destructor does not start. If your object has already done something that needs to be undone (for example, allocating some memory, opening a file or blocking a semaphore), this ā€œmaterial to be undoneā€ should be remembered by the data element inside the object.

Are these 2 statements not contradictory? The first of these implies that the catch attempt inside the constructor is practically useless, and the second says that it is necessary to free resources. What am I missing here?

+3
source share
5 answers

Moral No. 1 talks about the try-block function , and the second statement talks about the normal catch try block , both of which are completely different.

You need to understand the different between them in order to understand how these two sentences make sense. This answer here explains this.

+2
source

They refer to different things.

The first is associated with a try block, i.e. a try block, which includes an entire function, and in the case of constructors, also includes calls to base class designers and member objects, that is, material that is executed before the actual constructor body is executed.

The moral is that if the base class or a member of the class cannot be built correctly, the construction of the object must fail, because otherwise your newly constructed object will remain in an inconsistent state, half of the base objects / objects are built. Thus, the purpose of such a try block should only be to translate / recover such an exception, possibly to record the incident. You cannot do otherwise: if you did not explicitly specify throw inside your catch , the compiler adds an implicit throw; to prevent the likelihood of a "semi-constructed object."

The second relates to exceptions that are raised inside the body of your constructor; in this case, it says that you should use a ā€œnormalā€ try block to catch the exception, release the resources that you have allocated so far, and then throw it over since the destructor will not be called.

Note that this behavior makes sense, since the implicit contract for the destructor, as for any other member function that is not a constructor, is that it expects to work on the object in a consistent state; but an exception thrown from the constructor means that the object has not yet been fully constructed, so this contract will be violated.

+2
source

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(); } }; 
+2
source

Are these 2 statements not contradictory?

No. The second basically means that the constructor throws an exception that throws out of it (i.e. the Constructor), then the destructor is not called. And the first means that the try-block function does not allow the exception to exit the constructor. He catches it inside the constructor itself and processes it right there.

+1
source

Firstly, the constructor's try-block function does not match the try-catch inside the constructor.

Secondly, saying: ā€œthe material that needs to be undone must be remembered by the data memberā€ is not the same as saying ā€œuse try-catch blocks to undo thingsā€.

There is no contradiction between the two statements, they talk about different things. In fact, the second is not talking about trying to catch.

+1
source

Source: https://habr.com/ru/post/959206/


All Articles