copy & swap in base and derived class
Consider using = default
as much as possible. And if we are talking about public inheritance, you really need a virtual destructor as well.
Here is how your Base
would look using the copy/swap style:
class Base
{
int m_i;
public:
virtual ~Base() = default;
Base(const Base& other) = default;
Base& operator=(Base other) noexcept
{
swap(*this, other);
return *this;
}
Base(Base&& other) noexcept
: Base(0)
{
swap(*this, other);
}
explicit Base(int i) noexcept
: m_i{i}
{}
friend void swap(Base& a, Base& b) noexcept
{
using std::swap;
swap(a.m_i, b.m_i);
}
};
The only difference from what you have is that I've added the virtual destructor, and used = default
for the copy constructor.
Now for Derived
:
class Derived
: public Base
{
int m_j;
public:
Derived(const Derived& other) = default;
Derived& operator=(Derived other) noexcept
{
swap(*this, other);
return *this;
}
Derived(Derived&& other) noexcept
: Derived(0)
{
swap(*this, other);
}
explicit Derived(int j) noexcept
: Base(42)
, m_j{j}
{}
friend void swap(Derived& a, Derived& b) noexcept
{
using std::swap;
swap(static_cast<Base&>(a), static_cast<Base&>(b));
swap(a.m_j, b.m_j);
}
};
I've let the compiler implicitly take care of the destructor since the compiler will implicitly give me a virtual one that does the right thing in this case.
Again I've explicitly defaulted the copy constructor. This corrects a bug in your version which neglects to copy Base
.
The operator=
looks just like the Base
version.
The Derived
move constructor does not need to move or copy anything from other
since it is going to swap
with other
.
The Derived
swap
function must swap the Base
part as well as the Derived
part.
Now consider not using the copy/swap idiom. This can be surprisingly easier, and in some cases, higher performing.
For Base
you can use = default
for all 5 of your special members:
class Base
{
int m_i;
public:
virtual ~Base() = default;
Base(const Base&) = default;
Base& operator=(const Base&) = default;
Base(Base&&) = default;
Base& operator=(Base&&) = default;
explicit Base(int i) noexcept
: m_i{i}
{}
friend void swap(Base& a, Base& b) noexcept
{
using std::swap;
swap(a.m_i, b.m_i);
}
};
The only work that is really required here is your custom constructor and swap
function.
Derived
is even easier:
class Derived
: public Base
{
int m_j;
public:
explicit Derived(int j) noexcept
: Base(42)
, m_j{j}
{}
friend void swap(Derived& a, Derived& b) noexcept
{
using std::swap;
swap(static_cast<Base&>(a), static_cast<Base&>(b));
swap(a.m_j, b.m_j);
}
};
All 5 of the special members can be implicitly defaulted!
We couldn't default them in the Base
because we needed to specify the virtual destructor, which inhibits the generation of the move members, and the generation of the copy members is deprecated with a user-declared destructor. But since we do not need to declare the destructor in Derived
, we can just let the compiler handle everything.
As one of the big selling points of copy/swap is reduced coding, it can be ironic that using it can actually require more coding than letting the compiler default the special members.
Of course if the defaults do not do the right thing, then don't use them. I'm simply saying that the defaults should be your first choice, ahead of copy/swap.
You implement op=
exactly the same way for Derived
as for Base
:
Derived& operator=(Derived other) { swap(*this, other); return *this; }
I hope you are aware of the up- and down-sides of passing the argument by value there, though:
- Up-side: Only one function needed for all value categories.
- Down-Side: Second move for xvalues, move in addition to the needed copy for prvalues.
Other points to consider:
- Rule-of-thumb: Single-argument non-copy/move ctors should be
explicit
: You really don't want to have an implicit conversion fromint
toBase
... - You forgot to re-implement
swap
forDerived
(swap all sub-objects, both base and member). You might forego it ifDerived
does not add any members though.