shared_ptr cycles

C

Christopher

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

Chris M. Thomasson

Christopher said:
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;
}
______________________________________________________________
 
C

Christopher

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

James Kanze

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

Bart van Ingen Schenau

Christopher said:
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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top