Boost logo

Boost :

From: Chuck Messenger (chuckm_at_[hidden])
Date: 2003-05-27 13:24:41


Peter Dimov wrote:
> Chuck Messenger wrote:
>
>>Suppose you have this:
>>
>> struct X {
>> shared_ptr<Y> y;
>> ...
>> };
>>
>> struct Y {
>> X x;
>> ....
>> };
>>
>> struct Z {
>> Z() : pimpl_(new Y()) { pimpl_->x.y = pimpl_; }
>> shared_ptr<Y> pimpl_;
>> ...
>> };
>>
>>I'm using the "pimpl" idiom. When I create Z, I create an embedded Y
>>("y"), setting y's x so that it points to y.
>
>
> Why are you using a shared_ptr<Y> in X? A Y* or a weak_ptr<Y> seems more
> appropriate for parent links.

Suppose I have a cyclic dependency between A and B, which are
implemented with the pimpl idiom:

     #include <boost/shared_ptr.hpp>
     #include <iostream>

     using namespace boost;
     using namespace std;

     struct B_impl;

     struct B {
         shared_ptr<B_impl> pimpl_;
     };

     struct A_impl {
         A_impl() { cout << "new A\n"; }
         ~A_impl() { cout << "del A\n"; }
         B b_;
     };

     struct A {
         shared_ptr<A_impl> pimpl_;
         B get_B() const { return pimpl_->b_; }
     };

     struct B_impl {
         B_impl() { cout << "new B\n"; }
         ~B_impl() { cout << "del B\n"; }
         A a_;
     };

     A get_A(const B& b) { return b.pimpl_->a_; }
     B get_B(const A& a) { return a.pimpl_->b_; }

     A construct_A() {
         A a;
         a.pimpl_.reset(new A_impl); // a refcount is 1
         B& b = a.pimpl_->b_;
         b.pimpl_.reset(new B_impl); // b refcount is 1
         b.pimpl_->a_ = a; // a refcount is 2

         return a;
     }

     int main() {
         {
             A a = construct_A();
         }
         // ex-a's refcount is still 1, so object doesn't die
     }

OK, so we want to solve the problem that A and B keep eachother alive.

I don't see how a weak_ptr can be used, without breaking the whole way
I'm using A and B -- see get_A() and get_B().

>>The problem is that Y will now never die. What I'd like to do is to
>>decrement the reference count in x, when I construct the Y:
>>
>> Z() : pimpl_(new Y()) { pimpl_->x.y = pimpl_;
>> pimpl_.decrement_reference_count(); }
>>
>>This is perfectly sound -- it decrements the reference count from 2 to
>>1. It says, basically, "the mother structure contains a self-referring
>>pointer. If that's the only one left, then kill the mother structure."
>
> I wouldn't call it "perfectly sound". A pimpl_->x.y.reset() will leave your
> pimpl_ dangling and ~pimpl_ will crash.

Well, some experimentation proved that my "decrementing shared_ptr's
reference count" idea was flawed -- as you suggest, "not perfectly sound".

I came up with a way that did what I wanted, though, using
intrusive_ptr<>. What I do is to have a "master" object (possibly A or
B, or a 3rd object). I use intrusive_ptr_add_ref(A,B *) and
intrusive_ptr_release(A,B *) to increment/decrment the master object's
ref count.

In general, the abstraction is: you have a group of intra-referential
objects. When any of the group is constructed, they are all constructed
(so that the master count is temporarily > 1), and the master count is
reset to 1. When the master count goes to 0, the group is destructed.
Hence, the group only remains alive as long as there are any external
references (and as long as the intra-group references remain static).

Could a standard smart pointer be designed to encapsulate this
abstraction? Hmmm....

     - Chuck Messenger


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk