Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delegating to the default move constructor

Tags:

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?

like image 791
jleahy Avatar asked May 16 '13 11:05

jleahy


People also ask

Does STD move move constructor?

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.

Does compiler provide default move constructor?

Compilers provide a default: Constructor (no arguments) unless another constructor with arguments is declared.

What is the difference between a move constructor and a copy constructor?

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.

Does compiler create move constructor?

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.


1 Answers

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
};
like image 79
Jonathan Wakely Avatar answered Oct 03 '22 18:10

Jonathan Wakely