Boost logo

Boost :

From: Peter Dimov (pdimov_at_[hidden])
Date: 2003-08-26 13:04:20


E. Gladyshev wrote:
> I am using STL and trying to use boost in my daily
> development. What can I do to implement consistent classes in terms
> of memory management. For example, if I need to implement a class A
> that has a container and pointer to a data type. I have two options:
>
> template< typename T >
> struct A
> {
> shared_ptr<T> _data;
> std::list<T> _list;
> };
>
> template< typename T, template A = std::allocator<T> >
> struct A
> {
> shared_ptr<T> _data;
> std::list<T, A> _list;
> A : _data( new T() ) {...}
> };
>
> The problem with the first defenition is that I am limiting the
> built-in STL functionality in terms of memory management.
>
> The problem with the second definition is that if I expose the
> allocator template parameter, the user of my class will expect that
> all memory allocations of type T are going to be using her allocator
> but boost::shared_ptr doesn't support it.

It does... to an extent. You can't customize the count allocations, but they
aren't T allocations. For the T allocation you'll need something along the
lines of:

template<class A> struct X
{
    typedef typename A::value_type T;

    static T * create(A & a)
    {
        T * p = a.allocate(1);

        try
        {
            new(p) T;
        }
        catch(...)
        {
            a.deallocate(p, 1);
            throw;
        }

        return p;
    }

    static void destroy(A & a, T * p)
    {
        p->~T();
        a.deallocate(p, 1);
    }
};

The above doesn't respect A::pointer etc.

Now you can _almost_ do the straightforward thing:

template< class T, class A = std::allocator<T> >
struct Y
{
   shared_ptr<T> _data;
   std::list<T, A> _list;
   Y(): _data( X<A>::create(), X<A>::destroy ) {...}
};

except that X<A>::create and ::destroy take a reference to an allocator.
This should remind you that you forgot the allocator argument in the
constructor:

Y(A const & a = A()): ...

Now _list will make itself a copy of that 'a'. This immediately hints at a
potential problem:

template< class T, class A = std::allocator<T> > struct Z
{
   std::list<T, A> _list;
   std::list<T, A> _list2;
};

where _list and _list2 will make their own copies and can't be made to share
a single allocator instance. But let's ignore that (presumably the author of
a stateful A will use a <drumroll> shared_ptr<> underneath so that copies
use the same heap) and get back to our list+shared_ptr.

Now who should own the allocator that is used to construct or later destroy
*_data? Should we use _list.get_allocator() for that? We can't since a copy
of Y will copy _list but share _data, and the original may get destroyed
along with the allocator. So we'll need to do something along the lines of:

Y(A const & a = A()): _list(a), _data()
{
    A a2(a); // original is const
    T * p = X<A>::create(a2); // evaluation order
    _data.reset( p, bind(X<A>::destroy, a2, _1) );
}

Not very pretty, but that's what you need to pay for being
std::allocator-based; its interface is tailored for containers. For such a
case I'd definitely consider going back to

template< typename T > struct A
{
   shared_ptr<T> _data;
   std::list<T> _list;
};

unless there are very solid reasons for the allocator parameter. ;-)

Or you can write your own alloc_ptr<T, A>, of course, if you want to go the
allocator route; it is quite a different beast compared to shared_ptr as it
doesn't support polymorphism, for example, so it deserves its own name.

But your code still looks somewhat artificial to me; why would you want to
deep copy _list but shallow copy _data? It doesn't make much sense.


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