Why would I std::move an std::shared_ptr?

Solution 1:

I think that the one thing the other answers did not emphasize enough is the point of speed.

std::shared_ptr reference count is atomic. increasing or decreasing the reference count requires atomic increment or decrement. This is hundred times slower than non-atomic increment/decrement, not to mention that if we increment and decrement the same counter we wind up with the exact number, wasting a ton of time and resources in the process.

By moving the shared_ptr instead of copying it, we "steal" the atomic reference count and we nullify the other shared_ptr. "stealing" the reference count is not atomic, and it is hundred times faster than copying the shared_ptr (and causing atomic reference increment or decrement).

Do note that this technique is used purely for optimization. copying it (as you suggested) is just as fine functionality-wise.

Solution 2:

By using move you avoid increasing, and then immediately decreasing, the number of shares. That might save you some expensive atomic operations on the use count.

Solution 3:

Move operations (like move constructor) for std::shared_ptr are cheap, as they basically are "stealing pointers" (from source to destination; to be more precise, the whole state control block is "stolen" from source to destination, including the reference count information).

Instead copy operations on std::shared_ptr invoke atomic reference count increase (i.e. not just ++RefCount on an integer RefCount data member, but e.g. calling InterlockedIncrement on Windows), which is more expensive than just stealing pointers/state.

So, analyzing the ref count dynamics of this case in details:

// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);

If you pass sp by value and then take a copy inside the CompilerInstance::setInvocation method, you have:

  1. When entering the method, the shared_ptr parameter is copy constructed: ref count atomic increment.
  2. Inside the method's body, you copy the shared_ptr parameter into the data member: ref count atomic increment.
  3. When exiting the method, the shared_ptr parameter is destructed: ref count atomic decrement.

You have two atomic increments and one atomic decrement, for a total of three atomic operations.

Instead, if you pass the shared_ptr parameter by value and then std::move inside the method (as properly done in Clang's code), you have:

  1. When entering the method, the shared_ptr parameter is copy constructed: ref count atomic increment.
  2. Inside the method's body, you std::move the shared_ptr parameter into the data member: ref count does not change! You are just stealing pointers/state: no expensive atomic ref count operations are involved.
  3. When exiting the method, the shared_ptr parameter is destructed; but since you moved in step 2, there's nothing to destruct, as the shared_ptr parameter is not pointing to anything anymore. Again, no atomic decrement happens in this case.

Bottom line: in this case you get just one ref count atomic increment, i.e. just one atomic operation.
As you can see, this is much better than two atomic increments plus one atomic decrement (for a total of three atomic operations) for the copy case.

Solution 4:

Copying a shared_ptr involves copying its internal state object pointer and changing the reference count. Moving it only involves swapping pointers to the internal reference counter, and the owned object, so it's faster.

Solution 5:

There are two reasons for using std::move in this situation. Most responses addressed the issue of speed, but ignored the important issue of showing the code's intent more clearly.

For a std::shared_ptr, std::move unambiguously denotes a transfer of ownership of the pointee, while a simple copy operation adds an additional owner. Of course, if the original owner subsequently relinquishes their ownership (such as by allowing their std::shared_ptr to be destroyed), then a transfer of ownership has been accomplished.

When you transfer ownership with std::move, it's obvious what is happening. If you use a normal copy, it isn't obvious that the intended operation is a transfer until you verify that the original owner immediately relinquishes ownership. As a bonus, a more efficient implementation is possible, since an atomic transfer of ownership can avoid the temporary state where the number of owners has increased by one (and the attendant changes in reference counts).