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.