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:
- When entering the method, the
shared_ptr
parameter is copy constructed: ref count atomic increment. - Inside the method's body, you copy the
shared_ptr
parameter into the data member: ref count atomic increment. - 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:
- When entering the method, the
shared_ptr
parameter is copy constructed: ref count atomic increment. - Inside the method's body, you
std::move
theshared_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. - When exiting the method, the
shared_ptr
parameter is destructed; but since you moved in step 2, there's nothing to destruct, as theshared_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).