Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle constructors that must acquire multiple resources in an exception safe manner

I've got a non-trivial type that owns multiple resources. How do I construct it in an exception safe manner?

For example, here is a demo class X that holds an array of A:

#include "A.h"  class X {     unsigned size_ = 0;     A* data_ = nullptr;  public:     ~X()     {         for (auto p = data_; p < data_ + size_; ++p)             p->~A();         ::operator delete(data_);     }      X() = default;     // ... }; 

Now the obvious answer for this particular class is to use std::vector<A>. And that's good advice. But X is only a stand-in for more complicated scenarios where X must own multiple resources and it isn't convenient to use the good advice of "use the std::lib." I've chosen to communicate the question with this data structure simply because it is familiar.

To be crystal clear: If you can design your X such that a defaulted ~X() properly cleans everything up ("the rule of zero"), or if ~X() only has to release a single resource, then that is best. However, there are times in real life when ~X() has to deal with multiple resources, and this question addresses those circumstances.

So this type already has a good destructor, and a good default constructor. My question centers on a non-trivial constructor that takes two A's, allocates space for them, and constructs them:

X::X(const A& x, const A& y)     : size_{2}     , data_{static_cast<A*>(::operator new (size_*sizeof(A)))} {     ::new(data_) A{x};     ::new(data_ + 1) A{y}; } 

I have a fully instrumented test class A and if no exceptions are thrown from this constructor it works perfectly well. For example with this test driver:

int main() {     A a1{1}, a2{2};     try     {         std::cout << "Begin\n";         X x{a1, a2};         std::cout << "End\n";     }     catch (...)     {         std::cout << "Exceptional End\n";     } } 

The output is:

A(int state): 1 A(int state): 2 Begin A(A const& a): 1 A(A const& a): 2 End ~A(1) ~A(2) ~A(2) ~A(1) 

I have 4 constructions, and 4 destructions, and each destruction has a matching constructor. All is well.

However if the copy constructor of A{2} throws an exception, I get this output:

A(int state): 1 A(int state): 2 Begin A(A const& a): 1 Exceptional End ~A(2) ~A(1) 

Now I have 3 constructions but only 2 destructions. The A resulting from A(A const& a): 1 has been leaked!

One way to solve this problem is to lace the constructor with try/catch. However this approach is not scalable. After every single resource allocation, I need yet another nested try/catch to test the next resource allocation and deallocate what has already been allocated. Holds nose:

X(const A& x, const A& y)     : size_{2}     , data_{static_cast<A*>(::operator new (size_*sizeof(A)))} {     try     {         ::new(data_) A{x};         try         {             ::new(data_ + 1) A{y};         }         catch (...)         {             data_->~A();             throw;         }     }     catch (...)     {         ::operator delete(data_);         throw;     } } 

This correctly outputs:

A(int state): 1 A(int state): 2 Begin A(A const& a): 1 ~A(1) Exceptional End ~A(2) ~A(1) 

But this is ugly! What if there are 4 resources? Or 400?! What if the number of resources is not known at compile time?!

Is there a better way?

like image 664
Howard Hinnant Avatar asked Aug 05 '16 03:08

Howard Hinnant


People also ask

Can a constructor throws an exception How do you handle the error when the constructor fails?

Yes, constructors are allowed to throw an exception in Java. A Constructor is a special type of a method that is used to initialize the object and it is used to create an object of a class using the new keyword, where an object is also known as an Instance of a class.

Can we throw exception in constructor or destructor of the class Why?

Don't throw exceptions in constructors. @LaurentGrégoire: Creating and locking a mutex in the constructor would be pointless, because no one else would have a reference to said mutex, so it wouldn't protect anything.

What happens if an exception is thrown in a constructor?

When throwing an exception in a constructor, the memory for the object itself has already been allocated by the time the constructor is called. So, the compiler will automatically deallocate the memory occupied by the object after the exception is thrown.

What happens if an exception is thrown from an object's constructor and object's destructor?

Destructors are only called for the completely constructed objects. When the constructor of an object throws an exception, the destructor for that object is not called.

How to handle an exception in a constructor?

Now, for handling an exception there are two ways, one is to catch the exception and another is to throw it. But in the case of the constructor, we can’t handle it using the try-catch mechanism. The reason is that we enclose our code which can raise an exception in the try block and then catch it.

When to throw an illegalargumentexception in constructor?

Here, if the id passed to the Animal object is null, we can throw NullPointerException For arguments that are non-null but still invalid, such as a negative value for age, we can throw an IllegalArgumentException. Security checks are another common use case for throwing exceptions in the constructor.

How do I manage all the resources in a class?

Use smart pointers or other RAII-type wrappers to manage all resources. Avoid resource management functionality in your class destructor, because the destructor will not be invoked if the constructor throws an exception.

What happens if the arguments passed to the constructor are invalid?

Constructors are mostly used to assign values of variables. If the arguments passed to the constructor are invalid, we can throw exceptions. Let's consider a quick example: In the above example, we're performing argument validation before initializing the object. This helps to ensure that we're creating only valid objects.


2 Answers

Is there a better way?

YES

C++11 delivers a new feature called delegating constructors which deals with this situation very gracefully. But it is a bit subtle.

The problem with throwing exceptions in constructors is to realize that the destructor of the object you're constructing doesn't run until the constructor is complete. Though the destructors of sub objects (bases and members) will run if an exception is thrown, as soon as those sub objects are fully constructed.

The key here is to fully construct X before you start adding resources to it, and then add resources one at a time, keeping the X in a valid state as you add each resource. Once the X is fully constructed, ~X() will clean up any mess as you add resources. Prior to C++11 this might look like:

X x;  // no resources x.push_back(A(1));  // add a resource x.push_back(A(2));  // add a resource // ... 

But in C++11 you can write the multi-resource-acquizition constructor like this:

X(const A& x, const A& y)     : X{} {     data_ = static_cast<A*>(::operator new (2*sizeof(A)));     ::new(data_) A{x};     ++size_;     ::new(data_ + 1) A{y};     ++size_; } 

This is pretty much like writing code completely ignorant of exception safety. The difference is this line:

    : X{} 

This says: Construct me a default X. After this construction, *this is fully constructed and if an exception is thrown in subsequent operations, ~X() gets run. This is revolutionary!

Note that in this case, a default-constructed X acquires no resources. Indeed, it is even implicitly noexcept. So that part won't throw. And it sets *this to a valid X that holds an array of size 0. ~X() knows how to deal with that state.

Now add the resource of the uninitialized memory. If that throws, you still have a default constructed X and ~X() correctly deals with that by doing nothing.

Now add the second resource: A constructed copy of x. If that throws, ~X() will still deallocate the data_ buffer, but without running any ~A().

If the second resource succeeds, set the X to a valid state by incrementing size_ which is a noexcept operation. If anything after this throws, ~X() will correctly clean up a buffer of length 1.

Now try the third resource: A constructed copy of y. If that construction throws, ~X() will correctly clean up your buffer of length 1. If it doesn't throw, inform *this that it now owns a buffer of length 2.

Use of this technique does not require X to be default constructible. For example the default constructor could be private. Or you could use some other private constructor that puts X into a resourceless state:

: X{moved_from_tag{}} 

In C++11, it is generally a good idea if your X can have a resourceless state as this enables you to have a noexcept move constructor which comes bundled with all kinds of goodness (and is the subject of a different post).

C++11 delegating constructors is a very good (scalable) technique for writing exception safe constructors as long as you have a resource-less state to construct to in the beginning (e.g. a noexcept default constructor).

Yes, there are ways of doing this in C++98/03, but they aren't as pretty. You have to create an implementation-detail base class of X that contains the destruction logic of X, but not the construction logic. Been there, done that, I love delegating constructors.

like image 147
Howard Hinnant Avatar answered Sep 25 '22 17:09

Howard Hinnant


I think the problem stems from a violation of the Single Responsibility Principle: Class X has to deal with managing the lifetimes of multiple objects (and that is probably not even its main responsibility).

The destructor of a class should only free the resources that the class has directly acquired. If the class is just a composite (i.e. an instance of the class owns instances of other classes) it should ideally rely on automatic memory management (via RAII) and just use the default destructor. If the class has to manage some specialized resources manually (e.g. opens a file descriptor or a connection, acquires a lock or allocates memory) I would recommend factoring out the responsibility of managing those resources to a class dedicated for this purpose and then using instances of that class as members.

Using the standard template library would in fact help because it contains data structures (such as smart pointers and std::vector<T>) that exclusively handle this problem. They can also be composed, so even if your X has to contain multiple instances of objects with complicated resource acquisition strategies, the problem of resource management in an exception safe manner is solved both for each member as well as for the containing composite class X.

like image 25
Victor Savu Avatar answered Sep 24 '22 17:09

Victor Savu