This is more a styling than performance question. I have just converted (most of) my pointers to shared_ptr objects, and have reluctantly come to accept weak_ptrs as alternatives to raw pointers. My question is, what is the preferred method of iterating through a sequence (let's say a vector) of shared pointer objects? Here is what I've been doing:
std::vector<std::shared_ptr<A>> my_sequence;
// Do something to fill my_sequence;
for (std::shared_ptr<A> const& ptr : my_sequence)
{
ptr->AMethod();
}
This goes against the *don't use shared_ptr references* rule though, so what's a good alternative, and why?
Questions I would be asking are; Is the technique robust, ie. For AMethod() super tiny, and my_sequence super large, will this method start to impede performance unecessarily due to shared_ptr copies? Is it readable? Is it simple?
std::shared_ptr::getReturns the stored pointer. The stored pointer points to the object the shared_ptr object dereferences to, which is generally the same as its owned pointer.
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).
The smart pointer has an internal counter which is decreased each time that a std::shared_ptr , pointing to the same resource, goes out of scope – this technique is called reference counting. When the last shared pointer is destroyed, the counter goes to zero, and the memory is deallocated.
In short: Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.
First, a disclaimer:
shared_ptr
isn't a panacea. It should be used when ownership is actually shared. Non-sharing ownership is expressed by unique_ptr
and non-ownership is expressed by a (raw) reference. weak_ptr
is for a shared_ptr
that must be observed but not owned… but isn't a good defensive practice against stale pointers in general. Defaulting to shared_ptr
goes directly to the lowest common denominator; it's poor programming practice.
The choice here is between shared_ptr< T >
, shared_ptr< T const >
, shared_ptr< T > const &
, and T const &
, and T &
. Let's assume you're not changing anything, but the sequence could change its objects, so const
is desirable. That narrows it to shared_ptr< T const >
, shared_ptr< T > const &
, and T const &
.
Let's reformulate the question as,
Is
shared_ptr< T > const &
ever the right choice?
In this light, evaluate the alternatives.
shared_ptr< T const >
(pass shared pointer by value)
shared_ptr< T >
move
d to expand the ownership poolconst
correctness to the called functionshared_ptr< T > const &
(pass shared pointer by reference)
–: loses const
correctness to the called function
shared_ptr< T const > const &
instead, you will pass a temporary copy and end up with the disadvantages of this alternative and pass-by-value.–: access to object goes through an extra indirection
T const &
(direct reference)
const
correctnessWeighing it on balance, if ownership isn't being extended (such as by returning an object that retains the argument), you really should pass a simple reference. In such a case, the function should seldom care how its argument is owned. Having it require a shared_ptr
violates separation of concerns in the worst way. You can't have a local object or a member subobject and still use the function. And worst of all, everything is more complicated.
If ownership is potentially extended to someone else, which would be the justification for using shared_ptr
in the first place, then you should always pass by shared_ptr< [const] T >
value. Yes, it does copy the shared_ptr
upon call. But the expensive part of copying a shared_ptr
is updating the refcount, not pushing the pointers on the stack. And if you're conferring ownership, you want to update the refcount anyway. Take the passed value and move
it, which does not touch the refcount. Having done that, you're left with no advantages and a lot of disadvantages to pass-by-reference.
The reason for not using shared_ptr references is because you defeat the mechanism that it is trying to protect yourself from. I.e. having a potentially dangling pointer. If you are iterating through a container, this shouldn't be a problem since objects will not disappear on you unexpectedly. You just shouldn't store a shared_ptr reference which you may use later.
I.e. This is bad:
struct Y
{
int y;
Y() : y(1) {}
};
struct X
{
shared_ptr<Y>& ref_shared_ptr_y;
X(shared_ptr<Y>& y) : ref_shared_ptr_y(y) {}
};
void main()
{
shared_ptr<Y> shared_ptr_y(new Y());
X x(shared_ptr_y);
y.reset();
x.ref_shared_ptr_y->y; // UB since x.ref_shared_ptr_y was deleted
}
shared_ptr
should only be used if you really need object ownership to be shared between 2 or more locations. It causes unnecessary overhead otherwise and shows you haven't really thought the owner relationships through. If you have ownership limited to one location, use a unique_ptr
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With