The best option is option #3:
A(std::shared_pointer<B> b) : b_(std::move(b)) {} // option3: passing by value and move
Unlike option1
, it won't perform an unnecessary copy when the shared_ptr
was constructed from a prvalue passed to A
's constructor. Unlike option2
, it won't perform an unnecessary copy internally.
The costs are:
- When passed a prvalue, it performs a single construction and a single move construction (which is more efficient for
shared_ptr
, as it avoids needing to manipulate the reference count the way you have to if you copy and destroy the source)
- When passed any other r-value (e.g.
A(std::move(callers_ptr))
), it move constructs twice (again, avoiding any refcnt manipulation), but again, no copies
- When passed an l-value, it copies once (into the argument, taking ownership before the object is even constructed), then moves into the member cheaply. This is the case where the caller is maintaining ownership while also giving you separate ownership, so the single copy is unavoidable.
So the costs are:
- prvalue: One move (plus the necessary actual construction of the original
shared_ptr
)
- Other r-value: Two moves
- l-value: One move (plus necessary copy to acquire ownership)
For comparison, option #1 in each scenario requires:
- prvalue: One copy (plus the necessary construction of the original
shared_ptr
)
- Other r-value: One copy (possibly an additional move as well; not 100% on C++ rules for
const
reference lifetime extension in this scenario; either way, one copy is worse than two moves, given the atomics involved in shared_ptr
s)
- l-value: Just the necessary copy to acquire ownership (an improvement on option #3, which adds a move, but moves are cheap, so saving copies in the other cases is much more important)
Option #2 in each scenario is exactly the same as option #3, but one move from each scenario instead becomes a copy (so option #2 is objectively worse in every scenario); adding std::move
to change option #2 to option #3 is a pure win.
So yes, option #1 might be slightly more efficient if callers always retain their own ownership of the shared_ptr
while giving the A
its own ownership as well, never transferring their ownership to the new A
. But each move you're saving is just a couple non-atomic pointer assignments; either way you copy the pointer(s) from source to target, with move adding the NULL
ing out of the source, while saving a copy means you avoid incrementing the reference count atomically through the pointer to the non-local control block (likely to be an order of magnitude more expensive than local non-atomic pointer assignment).
Note: There is an option #4, as mentioned by Nathan in the comments that is strictly more performant, using a perfect-forwarding constructor to skip a move operation in each case. The downside is the code gets much more complicated, and if the use case is more complex (not just single trivial shared_ptr
member), you have the potential problems involved with the (usually not noexcept
) construction/copy operations occurring within the constructor, not on the caller side, so the risk of an exception occurring while the object is only partially constructed increases. As long as all non-move operations occur outside the constructor (meaning they're done for the arguments), the constructor itself can often be noexcept
and avoid needing to deal with the possibility of a mid-initialization exception.