shared_ptr cycles

Discussion in 'C++' started by Christopher, May 7, 2009.

  1. Christopher

    Christopher Guest

    I am not sure I understand this. I am need to before I get myself in
    trouble!

    "Because the implementation uses reference counting, cycles of
    shared_ptr instances will not be reclaimed. For example, if main()
    holds a shared_ptr to A, which directly or indirectly holds a
    shared_ptr back to A, A's use count will be 2. Destruction of the
    original shared_ptr will leave A dangling with a use count of 1. Use
    weak_ptr to "break cycles." "

    How would a shared pointer to A, directly or indirectly hold a shared
    pointer back to A?
    Shared pointers hold regular pointers as far as I know?

    This is the only situation I can come up with, which I would more
    easily describe as "If any shared pointer that already contains a raw
    pointer B, is assigned to a shared pointer that already contains B,
    the reference count is incremented and will not be decremented back to
    zero, when those shared pointers are destroyed" I am not even sure if
    that would happen, because ...isn't the reference count decremented
    when a shared pointer is assigned?

    int main()
    {
    boost::shared_ptr<MyClass> ptr1 = new MyClass();
    boost::shared_ptr<MyClass> ptr2 = ptr1; // increment ref count
    to 2

    ptr1 = ptr2; // decrement ref count for assignment and then
    increment?

    // what's the ref count? Is this what they are describing as a
    "cycle"?

    return 0;
    }

    I am not sure if that is the situation or not. I can't think of any
    other situation. Can someone please give examples?

    I am about to implement a copy constructor and an assingment operator
    for a class which contains shared pointers and I am wary that I might
    be creating "cycles" if I do that.

    I am also curious, if the above situation is what they are describing,
    can't I prevent it by:

    int main()
    {
    boost::shared_ptr<MyClass> ptr1 = new MyClass();
    boost::shared_ptr<MyClass> ptr2 = ptr1; // increment ref count
    to 2

    if( ptr1.get() != ptr2.get() )
    {
    ptr1 = ptr2;
    }

    return 0;
    }

    and if so, why didn't they do it in the implementation of the shared
    ptr?

    I am sure I am missing something...
    shared pointers have been working great so far, but I haven't had any
    copy construction or assignment of classes that contained them yet.
     
    Christopher, May 7, 2009
    #1
    1. Advertising

  2. "Christopher" <> wrote in message
    news:...
    >I am not sure I understand this. I am need to before I get myself in
    > trouble!
    >
    > "Because the implementation uses reference counting, cycles of
    > shared_ptr instances will not be reclaimed. For example, if main()
    > holds a shared_ptr to A, which directly or indirectly holds a
    > shared_ptr back to A, A's use count will be 2. Destruction of the
    > original shared_ptr will leave A dangling with a use count of 1. Use
    > weak_ptr to "break cycles." "
    >
    > How would a shared pointer to A, directly or indirectly hold a shared
    > pointer back to A?
    > Shared pointers hold regular pointers as far as I know?
    >
    > This is the only situation I can come up with, which I would more
    > easily describe as "If any shared pointer that already contains a raw
    > pointer B, is assigned to a shared pointer that already contains B,
    > the reference count is incremented and will not be decremented back to
    > zero, when those shared pointers are destroyed" I am not even sure if
    > that would happen, because ...isn't the reference count decremented
    > when a shared pointer is assigned?
    >
    > int main()
    > {
    > boost::shared_ptr<MyClass> ptr1 = new MyClass();
    > boost::shared_ptr<MyClass> ptr2 = ptr1; // increment ref count
    > to 2
    >
    > ptr1 = ptr2; // decrement ref count for assignment and then
    > increment?
    >
    > // what's the ref count? Is this what they are describing as a
    > "cycle"?
    >
    > return 0;
    > }
    > [...]


    That's not an example of a cycle; try something like this:


    <quick and dirty pseudo-code>
    ______________________________________________________________
    struct foo {
    boost::shared_ptr<foo> m_cycle;
    };

    int main() {
    {
    boost::shared_ptr<foo> p(new foo);
    p->m_cycle = p;
    }
    // the `foo' object created above is now leaked!
    return 0;
    }
    ______________________________________________________________
     
    Chris M. Thomasson, May 7, 2009
    #2
    1. Advertising

  3. Christopher

    Christopher Guest

    On May 6, 11:31 pm, "Chris M. Thomasson" <> wrote:
    > "Christopher" <> wrote in message
    >
    > news:...
    >
    >
    >
    >
    >
    > >I am not sure I understand this. I am need to before I get myself in
    > > trouble!

    >
    > > "Because the implementation uses reference counting, cycles of
    > > shared_ptr instances will not be reclaimed. For example, if main()
    > > holds a shared_ptr to A, which directly or indirectly holds a
    > > shared_ptr back to A, A's use count will be 2. Destruction of the
    > > original shared_ptr will leave A dangling with a use count of 1. Use
    > > weak_ptr to "break cycles." "

    >
    > > How would a shared pointer to A, directly or indirectly hold a shared
    > > pointer back to A?
    > > Shared pointers hold regular pointers as far as I know?

    >
    > > This is the only situation I can come up with, which I would more
    > > easily describe as "If any shared pointer that already contains a raw
    > > pointer B, is assigned to a shared pointer that already contains B,
    > > the reference count is incremented and will not be decremented back to
    > > zero, when those shared pointers are destroyed" I am not even sure if
    > > that would happen, because ...isn't the reference count decremented
    > > when a shared pointer is assigned?

    >
    > > int main()
    > > {
    > >   boost::shared_ptr<MyClass> ptr1 = new MyClass();
    > >   boost::shared_ptr<MyClass> ptr2 = ptr1;     // increment ref count
    > > to 2

    >
    > >   ptr1 = ptr2;  // decrement ref count for assignment and then
    > > increment?

    >
    > >   // what's the ref count? Is this what they are describing as a
    > > "cycle"?

    >
    > >   return 0;
    > > }
    > > [...]

    >
    > That's not an example of a cycle; try something like this:
    >
    > <quick and dirty pseudo-code>
    > ______________________________________________________________
    > struct foo {
    >   boost::shared_ptr<foo> m_cycle;
    >
    > };
    >
    > int main() {
    >   {
    >     boost::shared_ptr<foo> p(new foo);
    >     p->m_cycle = p;
    >   }
    >   // the `foo' object created above is now leaked!
    >   return 0;}
    >
    > ______________________________________________________________- Hide quoted text -
    >
    > - Show quoted text -




    I don't understand the underlying reason the reference count becomes
    incorrect. I need to understand it more thoroughly to prevent it.
    Can we walk through what the reference count is at each step?

    Here is what I envision, but might be incorrect:

    {
    boost::shared_ptr<foo> p(new foo); // ref count is 1
    p->m_cycle = p; // ref count becomes 2 because it
    was assigned
    } // it was actually assigned to
    m_cycle
    --- block end-----

    // p is being destroyed because it is out of scope
    // ref count becomes 1
    // When p is destroyed, m_cycle still contains a raw pointer that
    points to the address of foo
    // m_cycle is no longer accessable

    So, this scenario is when a smart pointer<t> that points to an t
    object that contains a smart pointer<t>
    If I make sure that no object contains smart pointers capable of
    pointing to the object type, am I safe?
    Are there more scenarios I should be wary of?

    I am trying to come up with some method of thinking that I can prevent
    a cycle from occuring as I implement my objects.

    So far, I beleive my current scenario to be a safe one:

    where I have an object that contains a smart pointer to differant
    object type.
    I implement a copy constructor for the object
    I assign the smart pointer member to the copied object's smart pointer
    member.
    I do the same in an assignment operator

    Should I be checking that the two smart pointers lhs and rhs do not
    already point to the same thing before assignment?
    Or am I safe?

    I want to have this down pat as other scenarios are sure to arise.
     
    Christopher, May 7, 2009
    #3
  4. Christopher

    James Kanze Guest

    On May 7, 1:37 pm, Christopher <> wrote:
    > On May 6, 11:31 pm, "Chris M. Thomasson" <> wrote:
    > > "Christopher" <> wrote in message


    > >news:....


    > > >I am not sure I understand this. I am need to before I get
    > > >myself in trouble!


    > > > "Because the implementation uses reference counting,
    > > > cycles of shared_ptr instances will not be reclaimed. For
    > > > example, if main() holds a shared_ptr to A, which directly
    > > > or indirectly holds a shared_ptr back to A, A's use count
    > > > will be 2. Destruction of the original shared_ptr will
    > > > leave A dangling with a use count of 1. Use weak_ptr to
    > > > "break cycles." "


    > > > How would a shared pointer to A, directly or indirectly
    > > > hold a shared pointer back to A? Shared pointers hold
    > > > regular pointers as far as I know?


    > > > This is the only situation I can come up with, which I
    > > > would more easily describe as "If any shared pointer that
    > > > already contains a raw pointer B, is assigned to a shared
    > > > pointer that already contains B, the reference count is
    > > > incremented and will not be decremented back to zero, when
    > > > those shared pointers are destroyed" I am not even sure if
    > > > that would happen, because ...isn't the reference count
    > > > decremented when a shared pointer is assigned?


    > > > int main()
    > > > {
    > > > boost::shared_ptr<MyClass> ptr1 = new MyClass();
    > > > boost::shared_ptr<MyClass> ptr2 = ptr1; // increment ref count
    > > > to 2


    > > > ptr1 = ptr2; // decrement ref count for assignment and then
    > > > increment?


    > > > // what's the ref count? Is this what they are describing as a
    > > > "cycle"?


    > > > return 0;
    > > > }
    > > > [...]


    > > That's not an example of a cycle; try something like this:


    > > <quick and dirty pseudo-code>
    > > ______________________________________________________________
    > > struct foo {
    > > boost::shared_ptr<foo> m_cycle;


    > > };

    >
    > > int main() {
    > > {
    > > boost::shared_ptr<foo> p(new foo);
    > > p->m_cycle = p;
    > > }
    > > // the `foo' object created above is now leaked!
    > > return 0;}


    > > ______________________________________________________________


    > I don't understand the underlying reason the reference count
    > becomes incorrect.


    It doesn't become incorrect. The rule for reference counting is
    that the object will be deleted when there are no more pointers
    to it. In the above, there will be no more pointers to the foo
    object in p only once the foo object has been deleted. And it
    won't be deleted until there are no more pointers to it.

    This is what is called a cycle: starting at one of the managed
    objects, you can navigate, using shared_ptr, through a cycle
    leading back to the original object. So even if all pointers
    external to the objects cease to exist, there are still pointers
    to them.

    The obvious solution to this problem is to use garbage
    collection for memory management, which frees the memory if no
    "reachable" pointers to it exist---that "reachable" makes a
    critical difference. Often, a better solution would be to not
    use dynamic allocation at all. And in many cases where dynamic
    allocation is necessary, the objects involved have definite
    lifetimes anyway, so no lifetime management is necessary (nor
    should be used, since it will violate the contract). In
    practice, there are really very few cases where shared_ptr is
    appropriate.

    > I need to understand it more thoroughly to prevent it. Can we
    > walk through what the reference count is at each step?


    > Here is what I envision, but might be incorrect:


    > {
    > boost::shared_ptr<foo> p(new foo); // ref count is 1
    > p->m_cycle = p; // ref count becomes 2 because it
    > was assigned} // it was actually assigned to


    > m_cycle
    > --- block end-----


    > // p is being destroyed because it is out of scope
    > // ref count becomes 1
    > // When p is destroyed, m_cycle still contains a raw pointer that
    > points to the address of foo
    > // m_cycle is no longer accessable


    > So, this scenario is when a smart pointer<t> that points to an
    > t object that contains a smart pointer<t> If I make sure that
    > no object contains smart pointers capable of pointing to the
    > object type, am I safe? Are there more scenarios I should be
    > wary of?


    Cycles may involve more than one object, and may involve objects
    of different types. If you never have a shared_ptr to an object
    which might contain a shared_ptr, you are safe. Otherwise, the
    analysis is more difficult. In practice, this isn't too great
    a problem. Objects to which shared_ptr is reasonable generally
    don't have shared_ptr, naturally. Provided you use shared_ptr
    intelligently.

    (In my own RefCntPtr, the objects pointed to must derive from
    RefCntObj. This is a lot more robust than shared_ptr, since you
    can't get two different counters for the same object, and by
    requiring the specific base class, it is easy to ensure that
    RefCntPtr can only point to objects which are designed with it
    in mind---and which, thus, don't contain RefCntPtr.)

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, May 7, 2009
    #4
  5. Christopher wrote:

    > On May 6, 11:31 pm, "Chris M. Thomasson" <> wrote:
    >>
    >> That's not an example of a cycle; try something like this:
    >>
    >> <quick and dirty pseudo-code>
    >> ______________________________________________________________
    >> struct foo {
    >> boost::shared_ptr<foo> m_cycle;
    >>
    >> };
    >>
    >> int main() {
    >> {
    >> boost::shared_ptr<foo> p(new foo);
    >> p->m_cycle = p;
    >> }
    >> // the `foo' object created above is now leaked!
    >> return 0;}
    >>
    >> ______________________________________________________________- Hide
    >> quoted text -
    >>
    >> - Show quoted text -

    >
    >
    >
    > I don't understand the underlying reason the reference count becomes
    > incorrect.


    That is because the reference count does not become incorrect. The
    problem is that the (shared) pointer referring to the foo object is also
    not accessible from the program.

    > I need to understand it more thoroughly to prevent it.
    > Can we walk through what the reference count is at each step?
    >
    > Here is what I envision, but might be incorrect:
    >
    > {
    > boost::shared_ptr<foo> p(new foo); // ref count is 1
    > p->m_cycle = p; // ref count becomes 2 because it
    > was assigned
    > } // it was actually assigned to
    > m_cycle
    > --- block end-----
    >
    > // p is being destroyed because it is out of scope
    > // ref count becomes 1
    > // When p is destroyed, m_cycle still contains a raw pointer that
    > points to the address of foo
    > // m_cycle is no longer accessable


    Yes, that is correct.
    The ref count remains (correctly) at 1, because there is still the
    m_cycle pointer that refers to the foo object.

    >
    > So, this scenario is when a smart pointer<t> that points to an t
    > object that contains a smart pointer<t>
    > If I make sure that no object contains smart pointers capable of
    > pointing to the object type, am I safe?
    > Are there more scenarios I should be wary of?


    You also have a potential cycle if:
    - class T contains a shared_ptr<U>, and
    - class U contains a shared_ptr<T>
    (or with even more classes involved)

    But rest assured, such designs are rare and usually suspect due to the
    circular references between the classes.

    The biggest risk for creating cycles in shared_ptr's is if you
    incorrectly use them to implement a doubly-linked list or tree structure
    or a circular list.
    Usually it is better to use standard containers instead of rolling your
    own, but if you must, you must not use shared_ptr because its semantics
    are wrong for that use.

    >
    > I am trying to come up with some method of thinking that I can prevent
    > a cycle from occuring as I implement my objects.


    The only way I know of is to use pen and paper and think about your
    design.
    - Draw a diagram of the classes and the structural connections between
    them
    - If the class diagram shows a loop somewhere, draw a diagram showing
    how the instances of the relevant classes are usually connected to each
    other.
    - If the instance diagram also shows a loop, ensure that at least one of
    the links is *not* implemented with a shared_ptr.

    >
    > So far, I beleive my current scenario to be a safe one:
    >
    > where I have an object that contains a smart pointer to differant
    > object type.
    > I implement a copy constructor for the object
    > I assign the smart pointer member to the copied object's smart pointer
    > member.
    > I do the same in an assignment operator


    With smart pointers, there is typically no need to implement a custom
    copy constructor or assignment operator. The compiler-generated ones
    will work correctly.

    >
    > Should I be checking that the two smart pointers lhs and rhs do not
    > already point to the same thing before assignment?
    > Or am I safe?


    There is no way to tell. Cycles between objects is a higher-level issue
    that can not be detected when writing code.

    >
    > I want to have this down pat as other scenarios are sure to arise.


    Bart v Ingen Schenau
    --
    a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
    c.l.c FAQ: http://c-faq.com/
    c.l.c++ FAQ: http://www.parashift.com/c -faq-lite/
     
    Bart van Ingen Schenau, May 7, 2009
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Taras_96
    Replies:
    2
    Views:
    948
    Taras_96
    Aug 22, 2005
  2. Allan Herriman

    Program for drawing clock cycles?

    Allan Herriman, Mar 21, 2006, in forum: VHDL
    Replies:
    2
    Views:
    1,582
    Thomas Thorsen
    Mar 21, 2006
  3. popman

    what is "web development cycles"?

    popman, May 23, 2004, in forum: ASP .Net
    Replies:
    1
    Views:
    400
    clintonG
    May 24, 2004
  4. Colin Caughie
    Replies:
    1
    Views:
    721
    Shooting
    Aug 29, 2006
  5. raj s
    Replies:
    4
    Views:
    943
    raj s
    Jul 28, 2008
Loading...

Share This Page