1

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?

11
  • 3
    @JHBonarius en.cppreference.com/w/cpp/memory/shared_ptr/operator%3D states clearly that the moved object remains empty – where's the UB? Commented Jan 18, 2022 at 15:13
  • 1
    Create another shared pointer inside your function and assign the rvalue reference to – then you should see the change you expect... Commented Jan 18, 2022 at 15:14
  • 1
    @JHBonarius "the object might be anything" is true for move operations in general, but a given move operation may specify exact behavior. This is the case for shared_ptr. Even the humble std::vector has a well defined state after being moved from: size shall be 0. Commented Jan 18, 2022 at 15:18
  • 2
    Although objects of some types may remain valid after move, I think the more important thing is to avoid use-after-move in general, even if it doesn't actually result in UB, just for good practice. Commented Jan 18, 2022 at 15:18
  • 1
    @JHBonarius We need a move construction of or assignment to another concrete object, not a reference, to see moving actually occurring. Commented Jan 18, 2022 at 15:20

2 Answers 2

7

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 } 
Sign up to request clarification or add additional context in comments.

Comments

3

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.