Arlo Belshee on 2 Jul 2003 20:11:01 -0000


[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

[ALACPP] Code for smart pointer to separate memory management strategy from allocator


Here's the code that I was thinking of in my last post. Please rip to pieces. ;)

/* allocators are policies, which meet the following interface. They simply
allocate & deallocate memory; they don't construct / destroy objects. */
class DefaultAllocator
{
  void* allocate(size_t s);
  void deallocate(void *p, size_t s);
};

// The managed ptr header:

namespace detail
{
  template< class _Allocator, int size >
  struct Destructor
  {
    void operator()(void *obj) {
      _Allocator.deallocate(obj, size); }
  };
}

template
<
  typename _T,
  template< typename > _MemoryManagementPtr = boost::shared_ptr,
  class _Allocator = DefaultAllocator
>
class CustomManagedPtr : public _MemoryManagementPtr< _T >
{
  // c'tors, op= & d'tors that match those for boost::shared_ptr,
  // and just inline a call to the base.

  typedef detail::Destructor< _Allocator, sizeof(_T) > Dest;
public:
  void Create() {
    void *mem(alloc_->allocate(sizeof(_T)));
    reset(new (mem) _T, Dest); }

  template< typename _Arg1T >
  void Create(_Arg1T arg1) {
    void *mem(alloc_->allocate(sizeof(_T)));
    reset(new (mem) _T(arg1), Dest); }

  // etc.
  // Actually, this template family would probably be built using Boost PP,
  // so that we could simply control the max number of c'tor args with
  // a single constant.

private:
  MonostatePtr< _Allocator > alloc_;
};

// Use:

// In headers where you select how you want to alloc things.
template< typename _T, template< class > _MemManagementStrategy =
boost::shared_ptr >
struct DefaultMemModel
{
  typedef CustomManagedPtr<
    _T,
    _MemManagementStrategy,
    MyFunnyAllocator >
  type;
};
// specializations, if you want them.

// defined in some other library:
void DoStuff(boost::shared_ptr< Foo > f);

// At point of use:
typedef DefaultMemModel< Foo, boost::shared_ptr >::type FooPtr;
typedef DefaultMemModel< Foo, boost::scoped_Ptr >::type ScopedFooPtr;
typedef CustomManagedPtr< Foo, boost::shared_ptr, MyAllocatorUsedOnlyHere >
  SpecialFooPtr;

FooPtr f;
f.Create("asdf", 10);
ScopedFooPtr f2;
f2.Create();
SpecialFooPtr f3;
f3.Create(*f);  // copy of f's foo, but allocated with different allocator.

DoStuff(f);
DoStuff(f3);

// The above code would look like the following with
// plain shared pointers instead:
typedef boost::shared_ptr< Foo > FooPtr;
typedef boost::scoped_ptr< Foo > ScopedFooPtr;

FooPtr f;
f.reset(new Foo("asdf", 10));
ScopedFooPtr f2;
f.reset(new Foo);
FooPtr f3;
f.reset(new Foo(*f));

DoStuff(f);
DoStuff(f3);

// Done.

Same except for the typedefs & the use of a Create instead of new Foo.
However, it allows custom allocators trivially. In particular, it moves the
selection of allocation method from the invocation to the typedef. Sometimes I
want to choose my allocator at time of use, but most often it will be
determined by my overall memory management strategy, and it's nice to be able
to wrap that up into a pointer typedef.

Note that this solution only works because boost has stated as part of their
spec that shared_ptr & scoped_ptr do not take any additional default template
args. Thus, I can use the template template parameter. Of course, I could get
the same behavior with a type computer as a policy, if I had to. It's just not
as pretty.

In general, the cusom management ptr is written to the union of the interface
of scoped_ptr & shared_ptr (also scoped_array & shared_array?), depending on
bad template instantiations to find errors in use.

The whole system works because the smart pointers take destructor functors at
the same time as they do the object that they are to manage. Then, the type of
the fucntor is lost to the compile-time system, and recovered via an internal
virtual function call at runtime. Thus, we only need the extended type info at
the time that we construct the smart pointer. So I simply inherit from the
smart pointer, and override only things that happen prior to object
construction. The fact that the object will be clipped when it is passed
around is irrelevant: the correct d'tor will be called.

Also, it is not a problem to reset the shared pointer to point to a different
object, even if that object was allocated from a different allocator. For
example:

{
  shared_ptr< Foo > f2
  {
    FooPtr f;
    f.Create();
    f2.reset(f);
  }
  f2.reset(new Foo);
}

Because any call to reset changes not only the object referenced, but the
destructor that will be called upon release, the reset in the last line
performs the following sequence:

1) Use the destructor that was set by create to destroy the first instance of
Foo, as it has now hit a ref count of 0. IE, call
SpecialAllocator.deallocate(args).
2) Change the object & allocator stored in f2 to be the new object, and the
default checked_delete (using delete).

Thus, when f2 goes out of scope, it will delete the second instance using the
default heap, just as if it had never been involved with the special
allocator. Method of deletion is determined purely by method of creation, not
by any intermediate type, as it should be.

Arlo


_______________________________________________
alacpp mailing list
alacpp@xxxxxxxxxxx
http://lists.ellipsis.cx/mailman/listinfo/alacpp