Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to free memory for shared_ptr circular references?

2 views
Skip to first unread message

tron....@verizon.net

unread,
Jan 16, 2006, 9:10:49 AM1/16/06
to
Given a program like the following:

#include <iostream>
#include <tr1/memory>

class Circular
{
public:
typedef std::tr1::shared_ptr<Circular> Ptr;

~Circular() { std::clog << "Destroying a circular instance.\n"; }
void SetReference(const Ptr& ptr) { m_ptr = ptr; }

private:
Ptr m_ptr;
};

int main()
{
Circular::Ptr first = Circular::Ptr(new Circular());
Circular::Ptr second = Circular::Ptr(new Circular());

first->SetReference(second);
second->SetReference(first);

return 0;
}

No output is produced when the program is run. This implies that the
destructors for the allocated objects are never called, and the memory
allocated is never freed up causing a memory leak.

The cause for this behavior is due to the circular references that
exist between the two instances.

How can someone ensure that memory is freed up when circular references
like this exist between tr1::shared_ptr's?


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Pete Becker

unread,
Jan 16, 2006, 1:29:30 PM1/16/06
to
tron....@verizon.net wrote:

>
> How can someone ensure that memory is freed up when circular references
> like this exist between tr1::shared_ptr's?
>

Simple: don't do it. <g>

You have to break the cycle. You can do that explicitly, by re-assigning
one of the shared_ptr objects to hold another resource (most likely with
a null pointer), or you can use a weak_ptr object instead of a
shared_ptr object as one of the links.

--

Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)

Larry

unread,
Jan 16, 2006, 1:29:08 PM1/16/06
to
On 01/16/2006 08:10 AM, tron....@verizon.net wrote:
(Sorry if the following is a repeat. I didn't get an ack
when sent from my newreader)
[snip]

>
> The cause for this behavior is due to the circular references that
> exist between the two instances.
>
> How can someone ensure that memory is freed up when circular references
> like this exist between tr1::shared_ptr's?


You might try the version of a "in development" boost policy_ptr tested
in:

http://tinyurl.com/derxd

the test with enumerator:

std_shared_graph_accepting

is precise and works with containers. The one with:

std_shared_graph_tagged

does not.

Preliminary docs are at:

HTH.

Attila Feher

unread,
Jan 16, 2006, 5:31:03 PM1/16/06
to
tron....@verizon.net wrote:
> Given a program like the following:
>
> #include <iostream>
> #include <tr1/memory>
>
> class Circular
> {
> public:
> typedef std::tr1::shared_ptr<Circular> Ptr;
>
> ~Circular() { std::clog << "Destroying a circular instance.\n"; }
> void SetReference(const Ptr& ptr) { m_ptr = ptr; }
>
> private:
> Ptr m_ptr;
> };
>
> int main()
> {
> Circular::Ptr first = Circular::Ptr(new Circular());
> Circular::Ptr second = Circular::Ptr(new Circular());
>
> first->SetReference(second);
> second->SetReference(first);
>
> return 0;
> }
>
> No output is produced when the program is run. This implies that the
> destructors for the allocated objects are never called, and the memory
> allocated is never freed up causing a memory leak.
>
> The cause for this behavior is due to the circular references that
> exist between the two instances.
>
> How can someone ensure that memory is freed up when circular references
> like this exist between tr1::shared_ptr's?

I am perhaps guessing here too much, but IMHO you need to use weak_ptr
references between those two objects, instead of shared_ptrs.

WW aka Attila

fre...@gmail.com

unread,
Jan 16, 2006, 5:44:23 PM1/16/06
to
> No output is produced when the program is run. This implies that the
> destructors for the allocated objects are never called, and the memory
> allocated is never freed up causing a memory leak.
>
> The cause for this behavior is due to the circular references that
> exist between the two instances.
>
> How can someone ensure that memory is freed up when circular references
> like this exist between tr1::shared_ptr's?
>

Once the program exits, the OS should free up all the memory the
program received. Obviously, the destructors won't be called in that
case. You may be able to avoid the cycles by using weak pointers
(tr1::weak_ptr) or by more careful design work.

In a pinch, and if the problem is limited to just a specific class in
your application, you could have objects of this class self-register in
some sort of pool (storing a weak_ptr to registrants). Free up cycles
using the pool by constructing a digraph based on inter object
relationships. Then use the use_count() to figure out if you have any
orphaned cycles. Of course, other parts of the application may still
be holding weak_ptrs to these orphans.

It would be easiest to simply reevaluate your design.

- Niek Sanders
http://www.cis.rit.edu/~njs8030/

Alberto Ganesh Barbati

unread,
Jan 17, 2006, 6:32:23 AM1/17/06
to
tron....@verizon.net wrote:
>
> How can someone ensure that memory is freed up when circular references
> like this exist between tr1::shared_ptr's?
>

If your design expects circular references to be present, then using
tr1::shared_ptr as a data member is not the right tool. You should use
tr1::weak_ptr instead.

HTH,

Ganesh

Nicola Musatti

unread,
Jan 18, 2006, 6:27:16 AM1/18/06
to

tron....@verizon.net wrote:
[...]

> How can someone ensure that memory is freed up when circular references
> like this exist between tr1::shared_ptr's?

An alternative to the use of weak_ptr is to move memory management
concerns to a layer below the one that has circular references. Your
example could be written as

#include <iostream>
#include <tr1/memory>

class Circular
{
public:
Circular() : m_ptr(0) {}


~Circular() { std::clog << "Destroying a circular instance.\n"; }

void SetReference(Circular * ptr) { m_ptr = ptr; }

private:
Circular * m_ptr;
};

typedef std::tr1::shared_ptr<Circular> CircularPtr;

int main()
{
std::pair<CircularPtr, CircularPtr> p;
p.first = CircularPtr(new Circular());
p.second = CircularPtr(new Circular());

p.first->SetReference(p.second);
p.second->SetReference(p.first);

return 0;
}

As the lifetime of both instances of Circular are now controlled by
std::pair you don't need to handle ownership/destruction within
circular itself.

Cheers,
Nicola Musatti

Carl Barron

unread,
Jan 19, 2006, 7:04:17 AM1/19/06
to
In article <1137502172.3...@g47g2000cwa.googlegroups.com>,
Nicola Musatti <nicola....@gmail.com> wrote:

> An alternative to the use of weak_ptr is to move memory management
> concerns to a layer below the one that has circular references. Your
> example could be written as
>
> #include <iostream>
> #include <tr1/memory>
>
> class Circular
> {
> public:
> Circular() : m_ptr(0) {}
> ~Circular() { std::clog << "Destroying a circular instance.\n"; }
> void SetReference(Circular * ptr) { m_ptr = ptr; }
>
> private:
> Circular * m_ptr;
> };
>
> typedef std::tr1::shared_ptr<Circular> CircularPtr;
>
> int main()
> {
> std::pair<CircularPtr, CircularPtr> p;
> p.first = CircularPtr(new Circular());
> p.second = CircularPtr(new Circular());
>

p.first->SetReference(p.second.get());
p.second->SetReference(p.first.get());
>
> return 0;
> }

It is much safer to use weak_ptr's and weak_ptr::lock() to get a
shared_ptr when one wants to access via m_ptr.
void Circular::use_ptr
{
boost::shared_ptr<Circular> p,q;
p = m_ptr.lock();
if(p!=q)
{
// pointer is valid ,use p to access data.
}
else
{
// pointer is invalid do something else
}
}
that something else might be do nothing if that is appropriate. The
point is that using raw pointers using Circular::m_ptr has no way to
know if the pointer has been deleted without its knowledge, but
weak_ptr provides this information.

Alberto Ganesh Barbati

unread,
Jan 20, 2006, 7:40:06 AM1/20/06
to
Carl Barron wrote:
> void Circular::use_ptr
> {
> boost::shared_ptr<Circular> p,q;
> p = m_ptr.lock();
> if(p!=q)
> {
> // pointer is valid ,use p to access data.
> }
> else
> {
> // pointer is invalid do something else
> }
> }

The following idiom will save you a few keystrokes:

void Circular::use_ptr()
{
if(boost::shared_ptr<Circular> p = m_ptr.lock())
{
// pointer is valid, use p to access data.


}
else
{
// pointer is invalid do something else
}
}

HTH,

Ganesh

Nicola Musatti

unread,
Jan 20, 2006, 3:11:33 PM1/20/06
to
[...]

> The point is that using raw pointers using Circular::m_ptr has no way to
> know if the pointer has been deleted without its knowledge, but
> weak_ptr provides this information.

My point is exactly to remove the need for weak_ptr's by centralizing
pointer management for a given context/subsystem. In this way within
that context you can assume that pointers are always valid.

Cheers,
Nicola Musatti

0 new messages