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.
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_; };
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.