Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I iterate through a sequence of shared_ptr objects?

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?

like image 954
quant Avatar asked Jun 24 '13 23:06

quant


People also ask

What does shared_ptr get () do?

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.

What happens when you move a shared_ptr?

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).

What happens when shared_ptr goes out of scope?

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.

Why would you choose shared_ptr instead of Unique_ptr?

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.


2 Answers

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)

    • +: straightforward value semantics; can pass shared_ptr< T >
    • +: can be copied or moved to expand the ownership pool
    • +: propagates const correctness to the called function
    • +: access to object is fast: a direct pointer argument is included
    • –: passing is expensive: copies two machine words and atomic-touches a ref count
    • –: called function concerns itself with ownership semantics
  • shared_ptr< T > const & (pass shared pointer by reference)

    • +: passing is as cheap as a direct object reference
    • +: can be copied to expand the ownership pool
    • –: loses const correctness to the called function

      • If you try 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

    • –: called function concerns itself with ownership semantics
  • T const & (direct reference)

    • +: preserves const correctness
    • +: caller unconcerned with ownership semantics
    • +: pass fast, just one machine word
    • +: access fast, direct pointer argument to function (if even that)
    • –: cannot confer ownership

Weighing 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.

like image 166
Potatoswatter Avatar answered Sep 18 '22 15:09

Potatoswatter


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.

like image 42
Adrian Avatar answered Sep 17 '22 15:09

Adrian