Search code examples
c++c++11movemove-semantics

How to implement a c++11 move function for a user-defined class?


I have a user-defined class (tree structure) with implemented move semantics, and a swap function. I would like to implement a move function the proper way, working as standard std::move implementation.

In the tree node class, each child node has a parent pointer, pointing to the parent node. This means that for move operations, all children have to reparented (and there may be many children)

This means that use of swap for moving is not optimal, as children of both lists have to be reparented after being swapped. So I would like to implement a move function which clears the moved-from tree.

The declaration of std::move implementations is somewhat complicated, they use a std::remove_reference<T>::type&& return type. Do I need this?


Solution

  • You don't need to write a specialisation of std::move.

    If you write a correct move constructor and move assignment operator, std::move will work on your class.

    example:

    #include <iostream>
    #include <cstring>
    
    using namespace std;
    
    struct Thing {
        Thing()
        : _data(new int[100])
        {
            cout << "default construct\n";
    
        }
    
        // Copy operator
        Thing(const Thing& other)
        : _data(new int[100])
        {
            cout << "copy constructor\n";
            memcpy(_data, other._data, sizeof(int) * 100);
        }
    
        // Move constructor
        Thing(Thing&& other) noexcept
        : _data(other._data)
        {
            cout << "move constructor\n";
            other._data = nullptr;
        }
    
        // assignment operator
        Thing& operator=(const Thing& rhs) {
            cout << "copy operator\n";
            if (&rhs != this) {
                Thing tmp(rhs);
                std::swap(*this, tmp);
            }
            return *this;
        }
    
        // move assignment operator
        Thing& operator=(Thing&& rhs) noexcept {
            cout << "move operator\n";
            std::swap(_data, rhs._data);
            return *this;
        }
    
    
        // destructor necessary since we are working in dangerous new/delete territory
        ~Thing() noexcept {
            cout << "destructor " << (_data ? "object has data" : "object is empty") << "\n";
    
            delete[] _data;
        }
    private:
        int* _data;
    };
    int main()
    {
        cout << "constructing a\n";
        Thing a;
    
        cout << "constructing b with copy of a\n";
        Thing b(a);
    
        cout << "moving a to newly constructed c\n";
        Thing c(std::move(a));
    
        cout << "moving c back to a\n";
        a = std::move(c);
    
        cout << "create a new d\n";
        Thing d;
        cout << "replace d with a copy of a\n";
        d = a;
    
        return 0;
    }
    

    Program's output:

    constructing a
    default construct
    constructing b with copy of a
    copy constructor
    moving a to newly constructed c
    move constructor
    moving c back to a
    move operator
    create a new d
    default construct
    replace d with a copy of a
    copy operator
    copy constructor
    move constructor
    move operator
    move operator
    destructor object is empty
    destructor object has data
    destructor object has data
    destructor object is empty
    destructor object has data
    destructor object has data