I often find myself writing tedious move constructors for classes with many member variables. They look something like the following:
A(A && rhs) :
a(std::move(rhs.a)),
b(std::move(rhs.b)),
c(std::move(rhs.c)),
d(std::move(rhs.d)) {
some_extra_work();
}
That is, they perform all of the actions associated with the default move constructor, then peform some mundane extra task. Ideally I would delegate to the default move constructor then perform the extra work, however the act of defining my own move constructor prevents the default implementation from being defined, meaning there's nothing to delegate to.
Is there a nice way to get around this anti-pattern?
std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.
Compilers provide a default: Constructor (no arguments) unless another constructor with arguments is declared.
If any constructor is being called, it means a new object is being created in memory. So, the only difference between a copy constructor and a move constructor is whether the source object that is passed to the constructor will have its member fields copied or moved into the new object.
In C++11 it was decided that the compiler will implicitly generate move constructor as member-wise moves, unless you have explicitly defined a copy constructor or copy/move assignment or a destructor.
Update: ignore the first part of this answer and skip to the end which has a better solution.
Wrap the extra work in a new type and inherit from it:
class A;
struct EW
{
EW(EW&&);
};
class A : private EW
{
friend class EW;
public:
A(A&&) = default;
};
EW::EW(EW&&) { A* self = static_cast<A*>(this); self->some_extra_work(); }
You could also do it with a data member instead of a base class, but you'd need some hackery using offsetof
(which is undefined for non-standard-layout types) or a hand-rolled equivalent using sneaky pointer arithmetic. Using inheritance allows you to use static_cast
for the conversion.
This won't work if some_extra_work()
has to be done after the members are initialized because base classes are initialized first.
Alternatively if the extra work is actually operating on the rvalue object that you're moving from, then you should wrap the members in types that do that work automatically when moved from, e.g. my tidy_ptr type, which I use to implement the Rule of Zero
class A
{
tidy_ptr<D> d;
public:
A() = default;
A(const A&) = default;
A(A&& r) = default; // Postcondition: r.d == nullptr
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With