Search code examples
c++c++11assignment-operatorrvalue-referencecopy-and-swap

Does it make sense to use the move-and-swap idiom on a movable and non-copyable class


If I have a class such as

class Foo{
public:
    Foo(){...}
    Foo(Foo && rhs){...}
    operator=(Foo rhs){ swap(*this, rhs);}
    void swap(Foo &rhs);
private:
    Foo(const Foo&);
// snip: swap code
};
void swap(Foo& lhs, Foo& rhs);

Does it make sense to implement operator= by value and swap if I don't have a copy constructor? It should prevent copying my objects of class Foo but allow moves.

This class is not copyable so I shouldn't be able to copy construct or copy assign it.

Edit

I've tested my code with this and it seems to have the behaviour I want.

#include <utility>
#include <cstdlib>
using std::swap;
using std::move;
class Foo{
public: Foo():a(rand()),b(rand()) {}
        Foo(Foo && rhs):a(rhs.a), b(rhs.b){rhs.a=rhs.b=-1;}
        Foo& operator=(Foo rhs){swap(*this,rhs);return *this;}
        friend void swap(Foo& lhs, Foo& rhs){swap(lhs.a,rhs.a);swap(lhs.b,rhs.b);}
private:
    //My compiler doesn't yet implement deleted constructor
    Foo(const Foo&);
private:
    int a, b;
};

Foo make_foo()
{
    //This is potentially much more complicated
    return Foo();
}

int main(int, char*[])
{
    Foo f1;
    Foo f2 = make_foo(); //move-construct
    f1 = make_foo(); //move-assign
    f2 = move(f1);
    Foo f3(move(f2));
    f2 = f3; // fails, can't copy-assign, this is wanted
    Foo f4(f3); // fails can't copy-construct

    return 0;
}

Solution

  • Move-and-swap is indeed reasonable. If you disable the copy constructor, then the only way that you can invoke this function is if you were to construct the argument with the move constructor. This means that if you write

    lhs = rhs; // Assume rhs is an rvalue
    

    Then the constructor of the argument to operator = will be initialized with the move constructor, emptying rhs and setting the argument to the old value of rhs. The call to swap then exchanges lhs's old value and rhs's old value, leaving lhs holding rhs's old value. Finally, the destructor for the argument fires, cleaning up lhs's old memory. As a note, this really isn't copy-and-swap as much as move-and-swap.

    That said, what you have now isn't correct. The default implementation of std::swap will internally try to use the move constructor to move the elements around, which results in an infinite recursion loop. You'd have to overload std::swap to get this to work correctly.

    You can see this online here at ideone.

    For more information, see this question and its discussion of the "rule of four-and-a-half."

    Hope this helps!