Boost logo

Boost :

From: Thomas Wenisch (twenisch_at_[hidden])
Date: 2002-07-19 11:16:08


Hello Navi, Peter, all,

On Thu, 18 Jul 2002, Navi Singh wrote:

> Peter Dimov:
>
> I am probably talking thru my hat. Don't know all the issues involved, but
> here is a suggestion. Is the following a bad idea? If not, is it feasible
> to provide a such a functionality in your smart ptr library.
>
> Consider two classes, one derives from counted base and one doesn't.
> class test1
> {
> };
> class test2 : public boost::counted_base
> {
>
> };
>
> As a rule, everyone will use a ptr template (defined later in this post).
> The ptr template will either be a shared_ptr or an intrusive_ptr template
> based on whether the class in consideration derives from counted_base or
> not.
>
> ptr<test1> t1(new test1); // ptr is a shared_ptr.
> ptr<test2> t2(new test2); // ptr is an intrusive ptr.
>

[ "helper" implementation details snipped ]

>
> template<class T, bool b = (Inheritance<T,
> boost::counted_base>::IsDerivedFromBase) >
> class Ptr : public If<b, boost::intrusive_ptr<T >, boost::shared_ptr<T >
> >::Result
> {
> typedef boost::intrusive_ptr<T > intrusiveT;
> typedef boost::shared_ptr<T > sharedT;
> typedef If<b, intrusiveT, sharedT >::Result baseT;
> public:
> // inline ~Ptr(){}
> inline Ptr():baseT(){}
> inline Ptr(baseT *x):baseT(x){}
> inline Ptr(const baseT& x):baseT(x){}
> inline baseT& operator=(const baseT& x){return baseT::operator=(x);}
> inline baseT& operator=(baseT *x){return baseT::operator=(x);}
>
> // inline operator T*() const {return baseT::operator*();}
> // inline T *operator->() const{return baseT::operator->();}
> //...
> //...
> // not sure what all needs to be here.
> //...
> };
>

This definately does work. I am using similar code to automatically
choose between shared_ptr and intrusive_ptr based on whether the value
type is derived from counted_base or not.

In my case, I am using this automatic decision code in an "aggregate
pointer" type. In my current project, we have "objects" which logically
represent a single entity, but where the actual state and methods that
make up this aggregate object are distributed across different software
components. Each "sub-object" is independant in the sense that it comes
from a different component, may live in a different inheritance heirarchy,
have a different allocation scheme, and be constructed completely
differently from other "sub-objects". At the same time, each component
wants to use different "slices" of the aggregate object - component A
wants sub-objects Foo & Bar, component B wants sub-objects Foo & Baz.

To make this more concrete, imagine that one component loads some
experimental data and manages the storage of that data, another two
components each do a complex calculation on that data and cache the
result, and a final component examines many individual experiments created
by the rest of the system, choosing which data to keep in memory, and
which to discard. The kicker is that the final component may choose to
keep the experimental data, or just the cached results of the
calculations, thus, the "sub-objects" of experiment have different
lifetimes. This is not exactly what we are doing, but it illustrates
the same design problem.

The C solution to managing these aggregate objects would be to pass a
struct with pointers to each slice. Each component contributes their
piece, and then the last component in line can choose to keep or dispose
of whatever it wants.

An OO solution would be to create an aggregate class that contains all the
sub-objects or inherits from all of them, provides accessors to the
sub-objects, or maybe even duplicates their interface and forwards to
them. My problem with this approach is that, if the set of sub-objects
and their lifetimes is constantly changing (we keep adding new
calculations, for example), maintaining this aggregate class is a pain.

Thus, our solution is to have a AggregatePointer template which takes a
typelist of the possible sub-objects. The aggregate pointer template uses
something very similar to GenScatterHeirarchy from Andrei Alexandrescu's
book to create a member variable to hold a pointer to each sub-object and
to provide overloaded set() and operator[] methods to set and access each
sub-object. Effectively, the AggregatePointer looks like an array of
pointers, but it is indexed by type tags instead of integers.

The pointers inside the AggregatePointer are either shared_ptr or
intrusive_ptr, depending on if the sub-object comes from counted-base or
not. Here, it is essential for AggregatePointer to make this decision,
the user should not have to specify which smart pointer to use. The
AggregatePointer object behaves as a smart pointer to the entire aggregate
object. It has value semantics, just like the smart pointers it is made
up of. Clearly, it is advantageous to minimize the size of
AggregatePointer by using intrusive_ptr's where possible.

In order to support the "slicing" of the aggregate object, and to allow
each sub-object to have independant life time, AggregatePointer objects
can be copied to other AggregatePointers whose typelists are a sub-set of
the original AggregatePointer. Thus, in my example above, if the
experiment manager component wants to hang on to the computed results but
not the original data, it "slices" off the pieces of the AggregatePointer
that it wants to keep, and drops the original AggregatePointer on the
floor. The smart pointers manage the memory automagically.

My point here is, there are situations where it is very desirable to
automatically choose intrusive_ptr over shared_ptr where possible. Here
is a case where the user makes this decision by choosing to derive or not
derive counted_base instead of explicitly choosing shared_ptr or
intrusive_ptr.

Note, however, that the big downside to this is that the complete
definition of each of the sub-objects must be available wherever the
AggregatePointer is used, or it might choose the wrong kind of
pointer. This could lead to Very Bad Things (one component uses a
shared_ptr with a detached count, everybody else is using the intrusive
ptr, object gets deleted prematurely). For me, this is not a problem,
since all the definitions are visible anyway. However, YMMV, and its an
important caveat to the technique.


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