Boost logo

Boost :

From: Gavin Collings (gcollings_at_[hidden])
Date: 1999-12-02 07:54:00

> > From: Andy Glew <glew_at_[hidden]>
> > I can imagine something like specifying the implementation,
> >
> > keepalive_ptr<T,reference_counted_share_ptr_handle_implementation>
> > or
> > keepalive_ptr<T,cyclic_ptr_GC_implementation>
> > where the second, optional, arguments are renamings of the present
> > shared_ptr and cyclic_ptr.
> >
> > I *think* that this implementation template parameter differs
> > significantly from an allocator.
> The point is that once you have added the allocator argument to
> shared_ptr you can partially specialize to do all kinds of things.

I think Andy's point is valid. I am part way through implementing a smart
pointer (interface defined by boost::shared_ptr) parameterized by sharing policy
and holding policy. Sharing policy for issues like intrusive vs external
reference counting, holding policy for value vs reference semantics. Recent
talk of parameterizing on an allocator was originally triggered by "Deleter" -
i.e. how the controlled object is allocated / deallocated. This seems
reasonable to me, but it also seems more to do with holding policy. What we're
talking about here is sharing policy. Of course, one can imagine a sharing
policy using allocators - even intrusive allocators that leave extra space for
reference counts, but that's a separate issue.
> > I can imagine that there may be an explicit
> keepalive_ptr<T,>::explicit_compre
> ss()
> > method, which might be a NOP for a reference counted implementation, or >
> > be the mark and sweep GC of cyclic_ptr.
> >
> > ---
> >
> > Q: am I missing something? Or could not both shared_ptr<T> and
> cyclic_ptr<T>
> > be viewed as implementations of what I call a keepalive_ptr<T>?
> Yes they could. I remain skeptical of the idea of specializing on
> implementation, because I am not sure what requirements to place on
> the implementation type. If we can nail that down I'd less worried.
> I am reluctantly coming around to specializing on allocator because
> we already have a spec for what an allocator is.

To kick things off, here is what I'm currently using for sharing policy. A few
points: -

- The template paramter is there to support intrusive sharing schemes.

- Implementations of this interface are designed to be derived from by the
smart pointer class

- In order to support derived-base conversions, we must be able to write

      template<typename U> ... xxx_sharing_policy<U>

   hence it is desirable to have nested template support i.e.

      template <typename T, template <typename> xxx_sharing_policy>
      class smart_pointer : xxx_sharing_policy<T>

   failing that, something like std::allocator<>::rebind<> could be done. The
best method of parameterisation remains unresolved, but doesn't affect the

- Parhaps an allocator parameter should be added - to be used for internal
allocations only (e.g. a reference count).

- In fact, due to the difficulties in maintaining the clear division between
holding and sharing policy (when the sharer needs access to what is held) I'm
seriously leaning towards combining the two. This would then reduce to the
implementation parameterisation described earlier. However, the distinction is
a useful one, and I'd like to keep it as separate as possible, and this
discussion is solely about sharing, so let's concentrate on that for now.

- To support garbage collection, an additional public member compress() could
be added.

- public, protected here are used as documentation to state the desired
visibility in the derived smart pointer class.

   template <typename T> class sharing_policy
      std::size_t use_count() const throw();
      bool unique() const throw();


      void swap( sharing_policy& other ) throw();

      void isolate();
      template <typename U>
      void join( const sharing_policy<U>& other ) throw();
      bool leave() throw();

   use_count() and unique() are shared_ptr sharing policy related
   public interface members included for implementation convenience.

   swap() helps the derived class implement its own.
   isolate(), join() and leave() form the guts. Think of the group of
   pointers sharing one particular object as a set.
   - isolate() creates a set with only one member (this)
   - join() adds this to other's set
   - leave() removes this from other's set, the bool return is a
               convenience telling the caller whether the set is
               now empty (necessitating tidy up)

It is the derived class's responsibility to call these in a sensible order. In
particular, it must ensure that the object is either isolated or joined after
every complete ownership operation.

Note the tentative exception specifications (based on experience of implementing
a few policies), in particular isolate might throw for a reference counted
policy implementation since that is where the count allocation would be done.

Boost list run by bdawes at, gregod at, cpdaniel at, john at