The rule of three is really the rule of five now. If you have a class that can be moved from, you should define the move semantics yourself (plus the copy, destructor, etc).
As to how to do that, to quote cppreference's page on std::move
, "... objects that have been moved from are placed in a valid but unspecified state." The unspecified state is typically what the object would look like if it had been default initialized, or what would happen if the objects had swap
called on them.
A straightforward way is, as @zenith has answered, is to have the move constructor (or assignment operator) set the original pointer to nullptr
. This way the data isn't freed, and the original object is still in a valid state.
Another common idiom is, as mentioned, to use swap
. If a class needs its own copy and move semantics, a swap
method would be handy as well. The move constructor would delegate initialization to the default constructor, and then call the swap
method. In a move assignment operator, just call swap
. The object being moved into will get the resources, and the other object's destructor will free the original ones.
It would usually look like this:
struct Foo
{
void* resource; //managed resource
Foo() : resource(nullptr) {} //default construct with NULL resource
Foo(Foo&& rhs) : Foo() //set to default value initially
{
this->swap(rhs); //now this has ownership, rhs has NULL
}
~Foo()
{
delete resource;
}
Foo& operator= (Foo&& rhs)
{
this->swap(rhs); //this has ownership, rhs has previous resource
}
void swap(Foo& rhs) //basic swap operation
{
std::swap(resource, rhs.resource); //thanks @M.M
}
};