Why does rvalue object does not get moved to function with rvalue parameter?
I was wondering why std::move
to a function with an rvalue parameter was not actually moving anything, but passing by reference instead?
Especially when I know it works for constructors.
I was running the following code:
#include <memory>
#include <iostream>
void consume_ptr(std::shared_ptr<int> && ptr) {
std::cout << "Consumed " << (void*) ptr.get() << std::endl;
}
int main(int argc, char ** argv)
{
std::shared_ptr<int> ptr = std::make_shared<int>();
consume_ptr(std::move(ptr));
if (ptr) {
std::cout << "ptr should be moved?" << std::endl;
}
return 0;
}
The output is:
ptr should be moved?
According to everything I've read, the std::shared_ptr
should have been moved inside the function, meaning that the object ptr
itself would hold nullptr
after moving it into consume_ptr
, but it doesn't!
I tried it with some custom class of mine with logging, and it looks like the move constructor is never even called. It's reproducible under every compiler and optimization level.
Can anyone clear this up for me please?
Solution 1:
std::move
by itself does not actually move anything. That is to say, std::move
alone does not invoke any move constructors or move-assignment operators. What it actually does is effectively cast its argument into an rvalue as if by static_cast<typename std::remove_reference<T>::type&&>(t)
, according to cppreference.
In order for any moving to actually happen, the moved object must be assigned to or used in the move-initialization of something else. For example, it could be used to initialize a member.
void something::consume_ptr(std::shared_ptr<int> && ptr) {
this->ptr = std::move(ptr);
std::cout << "Consumed " << (void*) ptr.get() << std::endl;
}
However, one way to make your pointer get moved without being assigned to anything is to simply pass it by value, causing your pointer to be moved into the parameter.
void consume_ptr(std::shared_ptr<int> ptr) {
std::cout << "Consumed " << (void*) ptr.get() << std::endl;
}
This way can actually be more useful than the rvalue way if you're going to end up assigning the parameter to something, because it allows you to pass stuff in by copy, too, and not just by move.
void consume_ptr_by_rvalue(std::shared_ptr<int> && ptr);
void consume_ptr_by_value(std::shared_ptr<int> ptr);
void do_stuff() {
std::shared_ptr<int> x = /*...*/;
std::shared_ptr<int> y = /*...*/;
// consume_ptr_by_rvalue(x); // Doesn't work
consume_ptr_by_rvalue(std::move(y)); // Risk of use-after-move
std::shared_ptr<int> z = /*...*/;
std::shared_ptr<int> w = /*...*/;
consume_ptr_by_value(z);
consume_ptr_by_value(std::move(w)); // Still risk, but you get the idea
consume_ptr_by_value(make_shared_ptr_to_something()); // Can directly pass result of something
}
Solution 2:
Here:
void consume_ptr( std::shared_ptr<int>&& ptr )
{
std::shared_ptr<int> new_ptr { ptr }; // calling copy ctor
std::cout << "Consumed " << ( void* ) new_ptr.get( ) << '\n';
}
No move happens there. No call to a move ctor or a move assignment operator. That's just a simple pass by rvalue reference there.
Output:
Consumed 0x20d77ebe6a0
ptr should be moved?
Now take a look at this:
void consume_ptr( std::shared_ptr<int>&& ptr )
{
std::shared_ptr<int> new_ptr { std::move(ptr) }; // calling move ctor
std::cout << "Consumed " << ( void* ) new_ptr.get( ) << '\n';
}
int main( )
{
std::shared_ptr<int> ptr { std::make_shared<int>( ) };
consume_ptr( std::move(ptr) );
if ( ptr )
{
std::cout << "ptr should be moved?" << std::endl;
}
}
Output:
Consumed 0x21160bbdf40
See. The move operation happened. And the if
's body did not run.