Hi, I want to ensure exception safety inside a constructor by creating
a temporary object and then swapping it with *this:
You have achieved a brittle exception safety (not very resistant to code
maintenance) at the cost of general safety.
class Test {
public:
Test(int value) : p(0)
{
Test tmp;
tmp.initialize(value); // In case of exception, tmp is destroyed
swap(tmp);
}
~Test()
{
if (p) delete p;
In passing, note that the `if` here is redundant.
};
swap(Test& other) {...}
In passing, note that it can help others' understanding if you mark the
`swap` as non-throwing, either through C++03 `throw()` or through C++11
`noexcept`, e.g., `swap( Test& other ) throw() { ... }`.
private:
Test() : p(0) {}
void initialize(int value)
{
p = new X(value, ...);
// and more members...
}
private:
X * p;
// And many more members...
};
The major problem here is potential copying of Test objects, whether
through copy construction or through copy assignment.
You will then have two or more objects that both/all think they're
responsible for deleting `p`, and which proceed to do that, with
resulting Undefined Behavior.
In C++98 and C++03 this is called the /rule of three/, that if you need
to define a destructor, a copy constructor or a copy assignment
operator, then you most probably need to define all three.
Or, instead of defining all those operations and making sure that
they're correct, do the sensible thing and use smart pointers and
containers instead of raw pointers and raw arrays.
I wanted to know:
a) Is this a common approach?
No.
b) What other options do you recommend when this is not possible
(e.g. Test has pure-virtual methods so I can't instantiate the tmp object)?
Well, the major option is ordinary C++ RAII throughout, which means:
if you have a data member that requires cleanup, then replace it with
a data member of a class that automates that cleanup.
So let's do that for the `p`.
A first try (intentionally imperfect) might look like this:
class AutoX
{
private:
X* px_;
public:
X* operator->() const { return px_; }
AutoX( int const value )
: px_( new X( value ) )
{}
~AutoX() { delete px_; }
};
The main problem with this class, as in the case of your code, is that
it violates the rule of three: value copying incurs multiple deletions
of the same object, which is Undefined Behavior.
So you need to decide
do you want to support value copying, or to prohibit value copying?
In passing, although not applicable to a class that just wraps a raw
pointer, an alternative to value copying is cloning.
Deciding that you don't need copying, the AutoX class is still simple:
class AutoX
{
private:
X* px_;
AutoX( AutoX const& ); // No such.
AutoX& operator=( AutoX const& ); // No such.
public:
X* operator->() const { return px_; }
AutoX( int const value )
: px_( new X( value ) )
{}
~AutoX() { delete px_; }
};
And then an exception safe Test class becomes just
class Test
{
private:
AutoX p_;
// And many more members...
Test( Test const& ); // No such.
Test& operator=( Test const& ); // No such.
public:
Test( int value )
: p_( value )
// and more members...
{}
};
There is one currently /unconventional feature/ here, namely that
something that behaves like a pointer, namely p, is constructed as it it
were an object of a non-pointer-like class (namely like an X).
I think in the years ahead that will become more and more common,
because C++11 supports general argument forwarding.
However, for C++03 you would have to use something like the forwarding
support I once presented at my blog, or completely manual class-specific
forwarding as in the code above. Neither choice feels totally clean. So
you might instead use a C++03 `std::auto_ptr` (note: as AutoX does not
support copying, and also, is deprecated in C++11):
class Test
{
private:
std::auto_ptr< X > p_;
// And many more members...
Test( Test const& ); // No such.
Test& operator=( Test const& ); // No such.
public:
Test( int value )
: p_( value )
// and more members...
{}
};
Cheers & hth.,
- Alf