DekGenius.com
Team LiB   Previous Section   Next Section

8.5 Allocators

An allocator is a policy class that defines an interface for managing dynamic memory. You already know about the new and delete expressions for allocating and freeing dynamic memory. They are simple, expressive, and useful, but the standard library does not necessarily use them internally. Instead, the standard library uses allocators, which let you provide alternative mechanisms for allocating and freeing memory.

The standard library provides a standard allocator (see <memory> in Chapter 13). If you don't want to use the standard allocator, you can use your own, provided it satisfies the same interface that is defined by the standard allocator.

8.5.1 Using Allocators

An allocator is a simple object that manages dynamic memory, abstracting new and delete expressions. All the container class templates take an allocator template parameter and use the allocator to manage their internal memory. You can use allocators in your own container classes or wherever you want to offer flexibility to the user of your class or template.

If you do not want to bother with allocators, you don't need to. All the standard containers have a default argument for their allocator template parameters: std::allocator, which uses standard new and delete expressions to manage dynamic memory.

If you write a new container class template, make sure it takes an allocator parameter, as the standard containers do. Use the allocator to manage internal memory for your container. See Chapter 10 for more information about containers.

If you simply want to use an allocator to manage memory, you can do so. Your class would use the allocator to allocate and free memory, initialize and finalize objects, and take the address of an allocated object. (See <memory> in Chapter 13 for a complete description of the allocator policy interface.) Example 8-4 shows a simple class that wraps a dynamic instance of any object. It is not particularly useful, but it illustrates how a class can use an allocator. Note how the allocation of memory is separated from the construction of the object. If allocate fails to allocate the desired memory, it throws bad_alloc, so the wrapper constructor fails before it tries to construct the object. If the allocation succeeds, but the call to construct fails, the memory must be freed, hence the try statement. The destructor assumes that wrapped class is well-written and never throws an exception.

Example 8-4. Wrapping a dynamic object
template<typename T, typename Alloc=std::allocator<T> >
class wrapper {
public:
  typedef T value_type;
  typedef typename Alloc::pointer pointer;
  typedef typename Alloc::reference reference;

  // Allocate and save a copy of obj.
  wrapper(const T& obj = T(  ), const Alloc& a = Alloc(  ))
  : alloc_(a), ptr_(0)
  {
    T* p = a.allocate(sizeof(T));
    try {
      alloc_.construct(p, obj);
    } catch(...) {
      // If the construction fails, free the memory without trying to finalize
      // the (uninitialized) object.
      alloc_.deallocate(p);
      throw;
    }
    // Everything succeeded, so save the pointer.
    ptr_ = p;
  }
  ~wrapper(  )
  {
    alloc_.destroy(ptr_);
    alloc_.deallocate(ptr_);
  }
  typename Alloc::reference operator*(  ) { return *ptr_; }
  value_type operator*(  ) const          { return *ptr_; }
private:
  Alloc alloc_;
  typename Alloc::pointer ptr_;
};

8.5.2 Custom Allocators

Writing a custom allocator requires care and patience. One particularly difficult point is that an implementation of the standard library is free to assume that all instances of an allocator class are equivalent, that is, allocators cannot maintain state. The standard permits this behavior without mandating it.

Thus, you need to be aware of the standard library's requirements for your implementation. Once you know the requirements, you can write a custom allocator. As a starting point, see Chapter 13 (under <memory>), which implements the allocator as trivial wrappers around new and delete expressions. Other allocators might manage memory that is shared between processes or differentiate between different kinds of pointers (such as near and far pointers found on old PC operating systems). More sophisticated allocators can implement debugging or validity checks to detect programmer errors, such as memory leaks or double frees.

    Team LiB   Previous Section   Next Section