-
Notifications
You must be signed in to change notification settings - Fork 319
BDE Allocator Model
This document provides information about the BDE allocator model, the rationale for its use, the available concrete allocators, and relevant code examples.
STL Allocators were introduced to provide containers an abstraction for the different pointer types on the Intel architecture (such as near and far pointers). That use was rendered obsolete after the C++ standard (section 20.1.5 of the 1998 standard) specified the requirements on an allocator type. The standard specification that all standard containers be parameterized on an allocator type made them independent of the underlying memory model.
Allocator objects provide users greater control over the memory usage of individual objects. By changing the allocation strategy users can control the memory source (for example: stack, heap, shared memory, etc.) e.g., memory for short-lived temporary objects can be supplied from fast, fixed size memory allocations on the stack. Additionally, allocator objects can be used to avoid fragmentation in long-running processes and to improve locality of reference among objects by colocating them.
Although standard allocators provide users great control on how containers can allocate memory, the C++03 allocator specification introduced two distinct problems:
-
The allocator used to supply memory to a container and its underlying objects is not the same. This issue was resolved with the introduction of the scoped_allocator_adaptor template in the C++11 standard (section 20.12).
-
Two containers instantiated with different allocator types refer to distinct types making interoperability between them difficult. A proposal for the use of Polymorphic allocators that would solve this issue is provided here.
BDE provides allocators and container implementations that solve the issues mentioned above. BDE provides an allocator protocol and concrete allocator implementations that can be passed as constructor arguments (not as template parameters) to all objects that allocate memory. The type of an object is unaffected by the passed-in allocator and the user has full control over the scope of an allocator instance. As the model specifies a protocol, it is easier to create concrete implementations and use them. The allocator model requires all elements (data members) of a container (object) to use the same allocator as the container (object). Also, the allocator is not transferred on copy construction.
bslma::Allocator
is a protocol for memory allocation mechanisms. The interface of this class includes two virtual methods allocate
and deallocate
whose signatures are:
virtual void *allocate(size_type size) = 0;
// Return a newly allocated block of memory of (at least) the specified
// positive 'size' (in bytes). If 'size' is 0, a null pointer is
// returned with no other effect. If this allocator cannot return the
// requested number of bytes, then it will throw a 'std::bad_alloc'
// exception in an exception-enabled build, or else will abort the
// program in a non-exception build. The behavior is undefined unless
// '0 <= size'. Note that the alignment of the address returned
// conforms to the platform requirement for any object of the specified
// 'size'.
virtual void deallocate(void *address) = 0;
// Return the memory block at the specified 'address' back to this
// allocator. If 'address' is 0, this function has no effect. The
// behavior is undefined unless 'address' was allocated using this
// allocator object and has not already been deallocated.
The bslma_allocator
component also provides overloaded global operator new
that allows convenient construction of objects using a bslma::Allocator
object. Although an overloaded global operator delete
is supplied in bslma_allocator
, it is solely for the compiler to invoke in the event an exception is thrown during a failed construction, and users should call the deleteObject
method instead to destroy an object and deallocate its memory. A code snippet showing the syntax of these methods is provided below:
template <typename TYPE>
void someFunction(bslma::Allocator *basicAllocator)
{
BSLS_ASSERT(basicAllocator);
TYPE *object = new (*basicAllocator) TYPE(...);
// Process 'object'
basicAllocator->deleteObject(object);
}
In many interfaces, the specified basicAllocator
is allowed to default to 0
to indicate that the the default allocator should be used:
template <typename TYPE>
void someOtherFunction(bslma::Allocator *basicAllocator = 0)
{
bslma::Allocator *allocator_p = bslma::Default::allocator(basicAllocator);
TYPE *object = new (*allocator_p) TYPE(...);
// Process 'object'
allocator_p->deleteObject(object);
}
Notice the use of the bslma::Default::allocator
utility function that, when passed 0
, returns the default allocator, and otherwise returns the given basicAllocator
.
A larger example of how to use this syntax is here.
Each BDE class that allocates memory requires an allocator at construction. This allocator argument provides clients fine-grained control over the memory usage of individual objects by substituting the default allocator with an object-specific alternative. Clients can write their own, customized allocator classes, and use those customized allocators with the BDE classes.
The BDE allocator model provides two static (global) allocator instances of which a user should be mindful:
-
Default allocator: This is the allocator that is used by default by all BDE objects. Users can access the default allocator by calling the
defaultAllocator
method inbslma::Default
. -
Global allocator: This is the allocator that should be used for constructing objects with static linkage. Users can access the global allocator by calling the
globalAllocator
method inbslma::Default
.
The ability to set the default or the global allocator is intended primarily for testing correct allocator propagation in composite types. Although code other than test drivers can set the default allocator by calling the setDefaultAllocator
method in bslma::Default
, doing so is strongly discouraged as changing the default allocator can lead to unintended effects in multi-threaded applications. If an application must set the default alloactor it must do so in the very beginning of main
and library code should never set these allocators. Once set, these allocators should remain in effect throughout the lifetime of an application. The bslma::NewDeleteAllocator
is the used for the default and global allocators unless they are explicitly modified.
Refer to the bslma_default
component for further information on the default and global allocators.
Three derived allocators are provided in the bslma
package:
-
MallocFreeAllocator
: This class uses the global functionsstd::malloc
andstd::free
for allocations and deallocations. Refer to thebslma_mallocfreeallocator
component for more information. -
NewDeleteAllocator
: This class uses global operatorsnew
anddelete
for allocations and deallocations, and is used for both the default and global allocators by default. Refer to thebslma_newdeleteallocator
component for more information. -
TestAllocator
: This class provides an instrumented memory allocator that uses global functionsstd::malloc
andstd::free
for allocations and deallocations, respectively. Instances of this class allow users to track memory usage and test the allocation behavior of objects. Refer to thebslma_testallocator
component for more information.
The bdlma::ManagedAllocator
, introduces a new allocator protocol, that derives from and extends the bslma::Allocator
protocol by adding a method to release all allocated memory at once. Derived implementations of this protocol can be used for allocating temporary memory and can be made efficient by skipping the deallocation of individual memory blocks.
The signature of the 'release' method is:
virtual void release() = 0;
// Release all memory currently allocated through this allocator.
Refer to the bdlma_managedallocator
component for more information.
The bdlma
package provides three derived bdlma::ManagedAllocator
allocators:
-
BufferedSequentialAllocator
: This class provides a fast allocator that dispenses heterogeneous blocks of memory (of varying, user-specified sizes) from an external buffer whose address and size (in bytes) are supplied at construction. Refer to thebdlma_bufferedsequentialallocator
component for more information. -
MultipoolAllocator
: This class maintains a configurable number of 'bdlma::Pool' objects, each dispensing maximally-aligned memory blocks of a unique size. Refer to thebdlma_multipoolallocator
component for more information. -
SequentialAllocator
: This class provides a fast allocator that dispenses heterogeneous blocks of memory (of varying, user-specified sizes) from a sequence of dynamically-allocated buffers. Refer to thebdlma_sequentialallocator
component for more information.
If objects of a class allocate memory (or contain data members that do) then having all constructors of that class accept the address of a bslma::Allocator
object as an argument allows its clients to control how those objects allocate memory. An example of this is provided by showing the creators of a Customer
class that stores the first and last names of a customer as bsl::string
objects and the various account numbers of that customer using a bsl::vector
:
Customer(bslma::Allocator *basicAllocator = 0);
Customer(const bslstl::StringRef& firstName,
const bslstl::StringRef& lastName,
const bsl::vector<int>& accounts,
int id,
bslma::Allocator *basicAllocator = 0);
Customer(const Customer& original, bslma::Allocator *basicAllocator = 0);
The constructor implementations of Customer
would simply forward the basicAllocator
argument to its data members:
Customer::Customer(bslma::Allocator *basicAllocator)
: d_firstName(basicAllocator)
, d_lastName(basicAllocator)
, d_accounts(basicAllocator)
, d_id(0)
{
}
Customer::Customer(const bslstl::StringRef& firstName,
const bslstl::StringRef& lastName,
const bsl::vector<int>& accounts,
int id,
bslma::Allocator *basicAllocator)
: d_firstName(firstName.begin(), firstName.end(), basicAllocator)
, d_lastName(lastName.begin(), lastName.end(), basicAllocator)
, d_accounts(accounts, basicAllocator)
, d_id(id)
{
BSLS_ASSERT_SAFE(!firstName.isEmpty());
BSLS_ASSERT_SAFE(!lastName.isEmpty());
}
Customer::Customer(const Customer& original,
bslma::Allocator *basicAllocator)
: d_firstName(original.d_firstName, basicAllocator)
, d_lastName(original.d_lastName, basicAllocator)
, d_accounts(original.d_accounts, basicAllocator)
, d_id(original.d_id)
{
}
All BSL containers, including bsl::string
and bsl::vector
, accept a bslma::Allocator
constructor argument.
Since the Customer
class contains members that allocate memory, it can associate the UsesBslmaAllocator
trait defined in the bslma
package to programmatically inform templated code that it uses an allocator as follows:
// TRAITS
namespace BloombergLP {
namespace bslma {
template <> struct UsesBslmaAllocator<Customer> : bsl::true_type {};
}
}
The complete Customer
class interface and implementation is provided here.
When writing templatized code that may be parameterized on types that allocate memory it is often necessary to decide whether to pass through the user-supplied allocator
to individual objects. Such code (and containers) can use the UsesBslmaAllocator
trait defined in the bslma
package to decide whether to pass the allocator to an object's constructor. An example of using this trait is provided below:
using namespace BloombergLP;
template <typename TYPE>
void appendDefaultElement(bsl::vector<TYPE> *array, bslma::Allocator *basicAllocator, bsl::false_type)
{
TYPE newElement;
array->push_back(newElement);
}
template <typename TYPE>
void appendDefaultElement(bsl::vector<TYPE> *array, bslma::Allocator *basicAllocator, bsl::true_type)
{
TYPE newElement(basicAllocator);
array->push_back(newElement);
}
template <typename TYPE>
void appendDefaultElement(bsl::vector<TYPE> *array, bslma::Allocator *basicAllocator)
{
appendDefaultElement(array, basicAllocator, bslma::UsesBslmaAllocator<TYPE>());
}
For further details refer to the bslma_usesbslmaallocator
component. A complete example that uses the UsesBslmaAllocator
trait is provided here.
Since bslma::Allocator
is a protocol, users can create their own concrete implementations for object-specific situations. A complete example of a concrete implementation that allocates memory from a user-supplied buffer and reverts to an allocator specified at construction if that buffer is exhausted is provided here.
Often objects that allocate memory are stored within standard containers. To allow users to control the allocation behavior of the container and its sub-elements, BSL containers pass the user-specified allocator through to individual elements if those elements allocate memory.
bdlma::BufferedSequentialAllocator allows users to supply a buffer used for allocating memory.
Functions often create short-lived containers that allocate memory from the heap. This is expensive and unnecessary since the memory is short-lived. The simple code snippet below shows how to create a bdlma::BufferedSequentialAllocator to that uses memory from the stack:
const int BUFFER_SIZE = 1024;
char buffer[BUFFER_SIZE];
bdlma::BufferedSequentialAllocator allocator(buffer, BUFFER_SIZE);
bsl::vector<int> dataVector(&allocator);
dataVector.resize(50);
The bslma::TestAllocator
provides an instrumented allocator that can be used to track various aspects of the memory allocated from it. The bslma_testallocator
component also provides macros that can be used to ensure the exception-safety of classes.
The bslma
package provides a range of components that can act as guards and proctors to manage previously allocated memory and objects. Refer to the bslma
package for further information on these components.
Alignment of an address in memory refers to the relative position of that address with respect to specific (hardware-imposed) boundaries within the memory space. An address can be said to be on a one-byte boundary, a two-byte boundary, a four-byte boundary, or an eight-byte boundary depending on whatever 1, 2, 4, or 8 is the largest integer that evenly divides the numerical value of that address. Refer to the bsls_alignment
component for further details on the memory alignment strategies supported.
Normally, programmers needn't worry about alignment for dynamically allocated memory. The runtime system's operator new
is required by the C++ standard to return memory blocks beginning at maximally-aligned addresses. All the BSL allocators similarly return memory that is maximally-aligned, and allocators
that return addresses that are "naturally aligned" (and therefore would be more efficient) are planned for subsequent releases.
Note that "alignment", as used by BDE components, does not deal with page boundaries or other larger memory structures, although those considerations may be important elsewhere.
When implementing the swap
method, classes whose objects store allocators should not exchange their respective allocators. An invocation of the swap
method is completed in constant time only if the allocators of the objects being swapped compare equal.
If an object that allocates memory is returned by-value then care must be taken to ensure that the allocator passed to that value outlives that object itself. With the return value optimization, returning by-value could result in objects referring to destroyed allocator instances. When returning objects that allocate by-value it is generally a good idea to use the default allocator.
All bslma
allocators (except the [bslma::TestAllocator
] (http://github.com/bloomberg/bde/blob/master/groups/bsl/bslma/bslma_testallocator.h#L299)) are fully thread-safe.
If you have questions, comments, suggestions for improvement or any other inquiries regarding BSL or this wiki, feel free to open an issue in the issue tracker.
BSL Wiki by Bloomberg Finance L.P. is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.