Reputation: 9292
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?
Upvotes: 4
Views: 6154
Reputation: 69864
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
Upvotes: 9
Reputation: 1077
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;
}
Upvotes: 2