Boost logo

Boost :

From: Chuck Messenger (chuckm_at_[hidden])
Date: 2003-05-29 07:25:33


Schoenborn, Oliver wrote:

> The above code does not make sense from a strict ownership point of view.

In other words, traditional smart pointers, such as NoPtr, are not
suitable when you need to support circular links.

> Indeed because A owns the pimpl_, the line before returning doesn't make
> sense for strict ownership. Without knowing more about your problem, there
> are two possibilities:
> - You always have A owns A_impl owns B owns B_impl refs A (what your
> original code seems to say), in this case B_impl contains an RRef<A> instead
> of a DynObj<A> and everything works
> - Your program can also have B owns B_impl owns A owns A_impl refs B, i.e.
> in some cases you have (indirectly) B owns A, other times A owns B. This one
> requires more thought, and strict ownership may not be the solution since
> this says that the ownership can have two forms.

Imagine that I'm supplying a network simulation library. I have:

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

     using namespace std;
     using namespace boost;

     struct Node;

     struct NodeImpl : noncopyable {
         NodeImpl(int id) : id_(id) { cout << id_ << " lives\n"; }
         ~NodeImpl() { cout << id_ << " dies\n"; }
     private:
         set<Node> nodes_;
         int id_;
         friend struct Node;
     };

     struct Node {
         Node(int id) : pimpl_(new NodeImpl(id)) { }
         Node(const Node& n) : pimpl_(n.pimpl_) { }
         void Connect(const Node& n) { pimpl_->nodes_.insert(n); }
         void Disconnect(const Node& n) { pimpl_->nodes_.erase(n); }
     private:
         shared_ptr<NodeImpl> pimpl_;
         friend bool operator<(const Node& n1, const Node& n2);
     };

     bool operator<(const Node& n1, const Node& n2) {
         return n1.pimpl_ < n2.pimpl_;
     }

     int main() {
         {
             Node n1(1);

             {
                 Node n2(2), n3(3), n4(4);

                 n1.Connect(n2);
                 n2.Connect(n2);
                 n3.Connect(n3);
                 n4.Connect(n4);
             }

             cout << "n1 points to the cluster {n2, n3, n4}\n";
         }

         cout << "n2, n3, n4 remain alive. That sucks.\n";
     }

The the problem space is essentially circular. There's nothing to be
done about it at a design level, in order to make reference counting work.

You could say, "the Node destructor needs to do Fancy Thing X". But
then, in effect, all you're doing is saying that the Node destructor
needs to run a garbage collection pass, which is exactly right. There's
no getting around it.

The point of a smart cyclic pointer is to perform this garbage
collection for you.

    - Chuck Messenger


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