In easy (practical) terms:
Copying an object means copying its "static" members and calling the new
operator for its dynamic objects. Right?
class A
{
int i, *p;
public:
A(const A& a) : i(a.i), p(new int(*a.p)) {}
~A() { delete p; }
};
However, to move an object (I repeat, in a practical point of view) implies only to copy the pointers of dynamic objects, and not to create new ones.
But, is that not dangerous? Of course, you could destruct a dynamic object twice (segmentation fault). So, to avoid that, you should "invalidate" the source pointers to avoid destructing them twice:
class A
{
int i, *p;
public:
// Movement of an object inside a copy constructor.
A(const A& a) : i(a.i), p(a.p)
{
a.p = nullptr; // pointer invalidated.
}
~A() { delete p; }
// Deleting NULL, 0 or nullptr (address 0x0) is safe.
};
Ok, but if I move an object, the source object becomes useless, no? Of course, but in certain situations that's very useful. The most evident one is when I call a function with an anonymous object (temporal, rvalue object, ..., you can call it with different names):
void heavyFunction(HeavyType());
In that situation, an anonymous object is created, next copied to the function parameter, and afterwards deleted. So, here it is better to move the object, because you don't need the anonymous object and you can save time and memory.
This leads to the concept of an "rvalue" reference. They exist in C++11 only to detect if the received object is anonymous or not. I think you do already know that an "lvalue" is an assignable entity (the left part of the =
operator), so you need a named reference to an object to be capable to act as an lvalue. A rvalue is exactly the opposite, an object with no named references. Because of that, anonymous object and rvalue are synonyms. So:
class A
{
int i, *p;
public:
// Copy
A(const A& a) : i(a.i), p(new int(*a.p)) {}
// Movement (&& means "rvalue reference to")
A(A&& a) : i(a.i), p(a.p)
{
a.p = nullptr;
}
~A() { delete p; }
};
In this case, when an object of type A
should be "copied", the compiler creates a lvalue reference or a rvalue reference according to if the passed object is named or not. When not, your move-constructor is called and you know the object is temporal and you can move its dynamic objects instead of copying them, saving space and memory.
It is important to remember that "static" objects are always copied. There's no ways to "move" a static object (object in stack and not on heap). So, the distinction "move"/ "copy" when an object has no dynamic members (directly or indirectly) is irrelevant.
If your object is complex and the destructor has other secondary effects, like calling to a library's function, calling to other global functions or whatever it is, perhaps is better to signal a movement with a flag:
class Heavy
{
bool b_moved;
// staff
public:
A(const A& a) { /* definition */ }
A(A&& a) : // initialization list
{
a.b_moved = true;
}
~A() { if (!b_moved) /* destruct object */ }
};
So, your code is shorter (you don't need to do a nullptr
assignment for each dynamic member) and more general.
Other typical question: what is the difference between A&&
and const A&&
? Of course, in the first case, you can modify the object and in the second not, but, practical meaning? In the second case, you can't modify it, so you have no ways to invalidate the object (except with a mutable flag or something like that), and there is no practical difference to a copy constructor.
And what is perfect forwarding? It is important to know that a "rvalue reference" is a reference to a named object in the "caller's scope". But in the actual scope, a rvalue reference is a name to an object, so, it acts as a named object. If you pass an rvalue reference to another function, you are passing a named object, so, the object isn't received like a temporal object.
void some_function(A&& a)
{
other_function(a);
}
The object a
would be copied to the actual parameter of other_function
. If you want the object a
continues being treated as a temporary object, you should use the std::move
function:
other_function(std::move(a));
With this line, std::move
will cast a
to an rvalue and other_function
will receive the object as a unnamed object. Of course, if other_function
has not specific overloading to work with unnamed objects, this distinction is not important.
Is that perfect forwarding? Not, but we are very close. Perfect forwarding is only useful to work with templates, with the purpose to say: if I need to pass an object to another function, I need that if I receive a named object, the object is passed as a named object, and when not, I want to pass it like a unnamed object:
template<typename T>
void some_function(T&& a)
{
other_function(std::forward<T>(a));
}
That's the signature of a prototypical function that uses perfect forwarding, implemented in C++11 by means of std::forward
. This function exploits some rules of template instantiation:
`A& && == A&`
`A&& && == A&&`
So, if T
is a lvalue reference to A
(T = A&), a
also (A& && => A&). If T
is a rvalue reference to A
, a
also (A&& && => A&&). In both cases, a
is a named object in the actual scope, but T
contains the information of its "reference type" from the caller scope's point of view. This information (T
) is passed as template parameter to forward
and 'a' is moved or not according to the type of T
.