Question

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?

Was it helpful?

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

OTHER TIPS

To write move semantics is to write move constructor/move assignment, but not to implement move function. As result, you maybe move the root of the another tree in move ctor/assignment.

class binary_tree_node {};

class binary_tree
{
public:
    binary_tree() : root(nullptr) {}

    binary_tree(binary_tree &&rhs)
        : root(rhs.root)
    {
        rhs.root = nullptr;
    }

    binary_tree& operator=(binary_tree rhs)
    {
        swap(rhs);
        return *this;
    }

    void swap(binary_tree &rhs)
    {
        std::swap(root, rhs.root);
    }

private:
    binary_tree_node *root;
};

int main()
{
    binary_tree tree1;
    binary_tree tree2 = std::move(tree1);

    return 0;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top