Top of document
©Copyright 1999 Rogue Wave Software

Building Your Own Allocators

Defining your own allocator is a relatively simple process. The Standard C++ Library describes a particular interface, consisting of types and functions. An allocator that conforms to the Standard must match the syntactic requirements for these member functions and types. The Standard C++ Library also specifies a portion of the semantics for the allocator type.

The Standard C++ Library allocator interface relies heavily on member templates. As of this writing, many compilers do not yet support both member function templates and member class templates. This makes it impossible to implement a standard allocator. Rogue Wave's implementation of the Standard C++ Library provides an alternative allocator interface that provides most of the power of the standard interface, without requiring unavailable compiler features. This interface differs significantly from the standard interface, and will not work with other vendors' versions of the Standard C++ Library.

We recommend that when you define an allocator and implement containers, you provide both the standard interface and the Rogue Wave interface. This will allow you to use allocators now, and to take advantage of the standard once it becomes available on your compiler.

The remainder of this section describes the requirements for the Standard C++ Library allocator, the requirements for Rogue Wave's alternative allocator, and some techniques that specify how to support both interfaces in the same code base.

Using the Standard Allocator Interface

An allocator that conforms to the Standard C++ Library allocator specification must have the following interface. The example uses my_allocator as a place holder for your own allocator name:

class my_allocator 
 {
   typedef implementation_defined size_type;
   typedef implementation_defined difference_type
   template <class T>
     struct types {
     typedef implementation_defined pointer;
     typedef implementation_defined const_pointer;
     typedef implementation_defined reference;
     typedef implementation_defined const_reference;
     typedef implementation_defined value_type;
   };
 

Each of the pointer types in this interface must have a conversion to void*. It must be possible to use the resulting void* as a this value in a constructor or destructor and in conversions to ::types<void>::pointer (for appropriate B) for use by B::deallocate().

Here is a description of the member functions that a Standard C++ Library allocator must provide:

my_allocator();
 my_allocator(const my_allocator&);
 ~my_allocator();
template <class T>
 types<T>::pointer address(types<T>::reference r) const;
template <class T>
 types<T>::const_pointer address(types<T>::const_reference r)
                         const;
template <class T>
 types<T>::pointer allocate(size_type n);
template <class T, class U>
 types<T>::pointer allocate(size_type n, U u);
template <class T>
 void 
 deallocate(types<T>::pointer);
size_type 
 max_size();
template <class T, class U>
 void 
 construct(types<T>::pointer p, U u);
template <class T>
 void 
 destroy(types<T>::pointer p);
template <class T>
 my_allocator::types<T>::pointer 
 operator new(my_allocator::types<T>::size_type, my_allocator&);
template <class T>
 my_allocator::types<T>::pointer 
 operator new[](my_allocator::types<T>::size_type,
                my_allocator&);
bool 
 operator==(const my_allocator& a, const my_allocator& b);

Using Rogue Wave's Alternative Interface

Rogue Wave provides an alternative allocator interface for those compilers that do not support both class templates and member function templates.

In this interface, the class allocator_interface provides all types and typed functions. Memory is allocated as raw bytes using the class provide by the Allocator template parameter. Functions within allocator_interface cast appropriately before returning pointer values. Because multiple allocator_interface objects can attach to a single allocator, one allocator can allocate all storage for a container, regardless of how many types are involved. The one real restriction is that pointers and references are hard-coded as type T* and T&. (Note that in the standard interface they are implementation_defined.). If your compiler supports partial specialization instead of member templates you can use it to get around even this restriction by specializing allocator_interface on just the allocator type.

To implement an allocator based on the alternative interface, supply the class labeled my_allocator below.

//
 // Alternative allocator uses an interface class
 // (allocator_interface)
 // to get type safety.
 //
 class  my_allocator
 {
   public:
     typedef implementation_defined size_type;
     typedef implementation_defined difference_type;
     my_allocator();
     ~my_allocator();
     void * allocate (size_type n, void *  = 0);
     void deallocate (void* p);
     size_type max_size (size_type size) const
 };
 

We've also included a listing of the full implementation of the allocator_interface class, to show how a standard container will use your class. The section entitled "Building Containers & Generic Algorithms" provides a full description of how the containers use the alternative interface.

template <class Allocator,class T>
 class allocator_interface 
 {
 public:
   typedef Allocator allocator_type;
   typedef T*         pointer;
   typedef const T*   const_pointer;
   typedef T&         reference;
   typedef const T&   const_reference;
   typedef T          value_type;
   typedef typename Allocator::size_type    size_type;
   typedef typename Allocator::difference_type difference_type;
 protected:
   allocator_type*         alloc_;
 public:
   allocator_interface() : alloc_(0) { ; }
   allocator_interface(Allocator* a) : alloc_(a) { ; }
   void alloc(Allocator* a)
   { 
     alloc_ = a; 
   }   
   pointer address (T& x) 
   { 
     return static_cast<pointer>(&x); 
   }
   size_type max_size ()  const
   { 
     return alloc_->max_size(sizeof(T));
   }
   pointer allocate(size_type n, pointer  = 0)
   {
     return static_cast<pointer>(alloc_->allocate(n*sizeof(T)));
   }
   void deallocate(pointer p)
   {
     alloc_->deallocate(p);
   }
   void construct(pointer p, const T& val)
   {
     new (p) T(val);
   }
   void destroy(T* p)
   {
     ((T*)p)->~T();
   }
 };
 class allocator_interface<my_allocator,void> 
 {
   public:
     typedef void*         pointer;
     typedef const void*   const_pointer;      
 };
 // 
 // allocator globals
 //
 void * operator new(size_t N, my_allocator& a);
 inline void * operator new[](size_t N, my_allocator& a);
 inline bool operator==(const my_allocator&, const my_allocator&);
 

How to Support Both Interfaces

Rogue Wave strongly recommends that you implement containers that support both the Standard C++ Library allocator interface, and our alternative interface. By supporting both interfaces, you can use allocators now, and take advantage of the standard once it becomes available on your compiler.

In order to implement both versions of the allocator interface, your containers must have some mechanism for determining whether the standard interface is available. Rogue Wave provides the macro RWSTD_ALLOCATOR in stdcomp.h to define whether or not the standard allocator is available. If RWSTD_ALLOCATOR evaluates to true, your compiler is capable of handling Standard C++ Library allocators, otherwise you must use the alternative.

The first place that you use RWSTD_ALLOCATOR is when determining which typenames the container must use to reflect the interface. To do this, place the equivalent of the following code in your container class definition:

#ifdef RWSTD_ALLOCATOR
     typedef typename Allocator::types<T>::reference
          reference;
     typedef typename Allocator::types<T>::const_reference
          const_reference;
     typedef typename Allocator::types<node>::pointer
          link_type;
     Allocator the_allocator;
 #else
     typedef typename
        allocator_interface<Allocator,T>::reference reference;
     typedef typename
        allocator_interface<Allocator,T>::const_reference
          const_reference;
     typedef typename 
        allocator_interface<Allocator,node>::pointer  link_type;
   Allocator alloc;
   allocator_interface<Allocator,T>  value_allocator;  
   allocator_interface<Allocator,node>  node_allocator;
 #endif
 

Notice that the alternative allocator (allocator_interface) has two parts: value_allocator and node_allocator. You will need to assemble these inside the constructor for your container, if you use the alternative allocator. In our example, the mechanism for initializing allocator_interface objects looks like this:

#ifndef RWSTD_ALLOCATOR
       node_allocator.alloc(alloc);
       value_allocator.alloc(alloc);
 #endif
 

Let's look at some examples of how we support both interfaces in calls to functions.

In this first example, the max_size member function will use the appropriate allocator object.

size_type max_size () const
 #ifdef RWSTD_ALLOCATOR 
     { return the_allocator.max_size(); }
 #else 
     { return node_allocator.max_size(); }
 #endif
 

A second example shows the use of the construct and address allocator functions to construct a new value on an existing location. The tmp object in this example is a node that contains a data member that is an actual stored value.

#ifdef RWSTD_ALLOCATOR
   the_allocator.construct(
         the_allocator.address((*tmp).data), x);
 #else
   value_allocator.construct(
         value_allocator.address((*tmp).data),x);
 #endif
 

Top of document