So move semantics is basically doing a shallow copy from an element that is about to die (go out of scope).
Say that we have object, dying_obj
, that we know is about to go out of scope (and hence have its destructor called), we can change its type to an rvalue
with std::move
like so:
other_obj = std::move(dying_obj);
std::move
is just a type cast. It has no run-time effect other than how its type is interpreted by the compiler for things like overloaded function resolution. In this case, constructor overloads.
It’s important the understand that this really is just a type flag, and the convention is that it refers to an object out-of-scope for its caller.
BUT.. to hammer the point home that rvalues
and lvalues
are just little flags, we could abuse these flags for other purposes (don’t ever do this!):
void increment (int & i)
{
++i;
}
void increment (int && i)
{
--i;
}
int main ()
{
// count up to 10:
for (int i = 0; i < 10; increment(i))
std::cout << i << " ";
// count down from 20:
for (int i = 20; i > 0; increment(std::move(i)))
std::cout << i << " ";
}
The above code uses an lvalue
as an “add” type and the rvalue
as a minus type. Very dumb, but also perfectly logical, it works correctly and with defined behaviour.
Getting back to the real world: when you call a function/method with an rvalue
object, like foo(std::move(obj))
, you’re signalling to the function that it can do as it likes with that object because it won’t be used by the caller any more. That’s the convention for what the signal means, but it’s just a signal. You can certainly rely on the STL library to follow this convention everywhere, and probably every single library that’s ever used by sane people.
Standard procedure is to do the move copy as efficiently as possible. Sadly we’re forced to copy all the values, but we’re not forced to copy all of the content that a pointer points to (this is where we do a shallow copy instead). For the pointers, we really must be careful to make sure that the destructor of dying_obj
won’t delete any of its allocations – there’s lots of ways to do this, actually, but the simplest is just to change any pointers to nullptr
after the copy. We could change the other values of dying_obj
, but we don’t need to and it takes time, so it’s typical to leave it as it is.
So it’s important to understand that the std::move(obj)
doesn’t invoke a destruction of the obj
– it will still go out of scope at the same place, so at that point its destructor will be called – which, if we’ve done our job correctly, will have no effect at all.
As for your precise problem:
pointer p1;
p1.val = 3;
*p1.my_pointer = 3;
pointer p2(std::move(p1));
printf("%d\n", p1.val);
printf("%d\n", p2.val);
With std::move(p1)
you’re communicating to the constructor that p1
won’t be used again, so it can pull out its contents (do the shallow copy), but then on the next line you show that you’re still using p1
:
printf("%d\n", p1.val);
So you’re breaking your promise.
1
solved Why not destory after calling std::move? [closed]