Provide utilities to set/fetch the default and global allocators.
More...
Namespaces |
namespace | bslma |
Detailed Description
- Outline
-
-
- Purpose:
- Provide utilities to set/fetch the default and global allocators.
-
- Classes:
bslma::Default | namespace for default/global allocator management utilities |
- See also:
- Component bslma_allocator, Component bslma_newdeleteallocator
-
- Description:
- This component provides a set of utility functions that manage the addresses of two distinguished memory allocators: the default allocator and the global allocator. Each of these allocators are of type derived from
bslma::Allocator
. Note that for brevity, in the following we will generally refer to "the address of the default allocator" as simply "the default allocator" (and similarly for the global allocator).
- The global allocator is intended to be used as the allocator for (global) singleton objects. In general, the default allocator is for all other memory allocations in contexts where an alternative allocator is not explicitly specified (or cannot be specified as, for example, when a compiler-generated temporary object of a type that requires an allocator is created).
- Initially, both the default allocator and global allocator resolve to the address of the
bslma::NewDeleteAllocator
singleton, i.e.: Methods are provided to retrieve and set the two allocators independently. The following two subsections supply further details, in turn, on the methods that pertain to the default and global allocators.
-
- Default Allocator:
- Two methods provide access to the default allocator,
bslma::Default::defaultAllocator
and bslma::Default::allocator
(the latter when called with no argument, or an explicit 0). When bslma::Default::allocator
is supplied with a non-0 argument, it simply returns that argument to the caller, (i.e., it acts as a pass-through). A (non-singleton) class that is designed to take advantage of an allocator will typically revert to the default allocator whenever a constructor is called without an allocator (yielding the default argument value of 0). The bslma::Default::allocator
method facilitates this behavior. See the usage examples below for an illustration of this technique.
- The default allocator can be set prior to a call to
bslma::Default::defaultAllocator
, to bslma::Default::allocator
with no argument or an explicit 0, or to bslma::Default::lockDefaultAllocator
, by calling bslma::Default::setDefaultAllocator
. This method returns 0 on success and a non-zero value on failure. This method fails if the default allocator is "locked". The default allocator is initially unlocked. It is explicitly locked by calling bslma::Default::lockDefaultAllocator
. In addition, the default allocator is implicitly locked as a side-effect of calling bslma::Default::defaultAllocator
, or bslma::Default::allocator
with no argument or an explicit 0. Once locked, the default allocator cannot be unlocked. However, the bslma::Default::setDefaultAllocatorRaw
method will unconditionally set the default allocator regardless of whether it is locked.
- A well-behaved program should call
bslma::Default::setDefaultAllocator
once. It should be invoked in main
before starting any threads, and be followed immediately by a call to 'bslmaDefault::lockDefaultAllocator. Note that bslma::Default::setDefaultAllocatorRaw
is provided for testing only, and should typically never be used in a production environment.
- WARNING: Note that the default allocator can become locked prior to entering
main
as a side-effect of initializing a file-scope static object. For example, the presence of a global bsl::string
object in an executable will have this unintended consequence. Further note that this phenomenon can vary across platforms. In particular, linkers differ as to the aggressiveness with which they pull in file-scope static objects from the libraries that are on the link line. AVOID file-scope static objects that require runtime initialization, especially those that take an allocator.
-
- Global Allocator:
- The interface pertaining to the global allocator is comparatively much simpler, consisting of just two methods. The
bslma::Default::globalAllocator
method, when called with no argument (or an explicit 0), returns the global allocator currently in effect at the point of call. It has no side-effects. When supplied with a non-0 argument, bslma::Default::globalAllocator
simply returns that argument to the caller (i.e., it acts as a pass-through similar to bslma::Default::allocator
when it is supplied with a non-0 argument). The global allocator may be set using the bslma::Default::setGlobalAllocator
method. This method always succeeds. In that respect, the global allocator cannot become locked like the default allocator. bslma::Default::setGlobalAllocator
returns the global allocator that is in effect upon entry to the function.
- Note that
bslma::Default::setGlobalAllocator
should be used with extreme caution. In particular, a well-behaved program should call this function at most once. If called, it should be invoked in main
before starting any threads and before initializing singletons.
-
- Usage:
- The following sequence of usage examples illustrate recommended use of the default and global allocators. The examples employ the following simple memory allocator,
my_CountingAllocator
, that counts both the number of memory blocks that have been allocated, but not yet deallocated, and the cumulative number of blocks ever allocated. The two values are available through the accessors numBlocksInUse
and numBlocksTotal
, respectively. For actual allocations and deallocations, my_CountingAllocator
uses global operators new
and delete
:
#include <bslma_allocator.h>
class my_CountingAllocator : public bslma::Allocator {
int d_numBlocksInUse;
int d_numBlocksTotal;
my_CountingAllocator(const my_CountingAllocator&);
my_CountingAllocator& operator=(const my_CountingAllocator&);
public:
my_CountingAllocator();
virtual ~my_CountingAllocator();
virtual void *allocate(size_type size);
virtual void deallocate(void *address);
int numBlocksInUse() const;
int numBlocksTotal() const;
};
inline
my_CountingAllocator::my_CountingAllocator()
: d_numBlocksInUse(0)
, d_numBlocksTotal(0)
{
}
inline
int my_CountingAllocator::numBlocksInUse() const
{
return d_numBlocksInUse;
}
inline
int my_CountingAllocator::numBlocksTotal() const
{
return d_numBlocksTotal;
}
The virtual
methods of my_CountingAllocator
are defined in the component .cpp
file:
#include <my_countingallocator.h>
my_CountingAllocator::~my_CountingAllocator()
{
}
void *my_CountingAllocator::allocate(size_type size)
{
++d_numBlocksInUse;
++d_numBlocksTotal;
return ::operator new(size);
}
void my_CountingAllocator::deallocate(void *address)
{
--d_numBlocksInUse;
::operator delete(address);
}
-
- Example 1: Basic Default Allocator Use:
- This usage example illustrates the basics of class design that relate to proper use of the default allocator, and introduces the standard pattern to apply when setting (and locking) the default allocator. First we define a trivial class,
my_Id
, that uses an allocator. my_Id
simply encapsulates a C-style (null-terminated) id string that is accessible through the id
method. Note that each constructor is declared to take an optional bslma::Allocator *
as its last argument. Also note that the expression: is used in applicable member initializers to propagate each constructor's allocator argument to the data members that require it (in this case, the object allocator that is held by each my_Id
object). If basicAllocator
is 0, the object is created using the default allocator. Otherwise, the explicitly supplied allocator is used:
#include <bslma_allocator.h>
#include <bslma_default.h>
class my_Id {
char *d_buffer_p;
bslma::Allocator *d_allocator_p;
my_Id& operator=(const my_Id&);
public:
explicit my_Id(const char *id, bslma::Allocator *basicAllocator = 0);
my_Id(const my_Id& original, bslma::Allocator *basicAllocator = 0);
~my_Id();
const char *id() const;
};
inline
my_Id::my_Id(const char *id, bslma::Allocator *basicAllocator)
: d_allocator_p(bslma::Default::allocator(basicAllocator))
{
d_buffer_p = (char *)d_allocator_p->allocate(std::strlen(id) + 1);
std::strcpy(d_buffer_p, id);
}
inline
my_Id::my_Id(const my_Id& original, bslma::Allocator *basicAllocator)
: d_allocator_p(bslma::Default::allocator(basicAllocator))
{
const char *id = original.id();
d_buffer_p = (char *)d_allocator_p->allocate(std::strlen(id) + 1);
std::strcpy(d_buffer_p, id);
}
inline
my_Id::~my_Id()
{
d_allocator_p->deallocate(d_buffer_p);
}
inline
const char *my_Id::id() const
{
return d_buffer_p;
}
Next we set the default allocator to one of our counting allocator objects. Note that immediately after successfully setting it, we lock the default allocator, so that subsequent calls to bslma::Default::setDefaultAllocator
fail. (The default allocator can still be modified by calling bslma::Default::setDefaultAllocatorRaw
, but calling that function in production code is anti-social. Our usage examples expressly do not call that method.) With the possible exception of test drivers, the default allocator should be set and locked early in main
before threads are started and before objects are initialized: In the following, we instantiate two objects of type my_Id
. The first object, idA
, is not supplied with an allocator, so it uses the default allocator. The second object, idB
, is supplied with an object of type my_CountingAllocator
. The assertions track the states of the two allocators at each point in the code fragment. In particular, note that the state of the default allocator does not change during the lifetime of idB
: assert(0 == defaultCountingAllocator.numBlocksInUse());
assert(0 == defaultCountingAllocator.numBlocksTotal());
{
my_Id id("A");
assert(1 == defaultCountingAllocator.numBlocksInUse());
assert(1 == defaultCountingAllocator.numBlocksTotal());
}
assert(0 == defaultCountingAllocator.numBlocksInUse());
assert(1 == defaultCountingAllocator.numBlocksTotal());
my_CountingAllocator objectCountingAllocator;
assert(0 == objectCountingAllocator.numBlocksInUse());
assert(0 == objectCountingAllocator.numBlocksTotal());
{
my_Id idB("B", &objectCountingAllocator);
assert(1 == objectCountingAllocator.numBlocksInUse());
assert(1 == objectCountingAllocator.numBlocksTotal());
assert(0 == defaultCountingAllocator.numBlocksInUse());
assert(1 == defaultCountingAllocator.numBlocksTotal());
}
assert(0 == objectCountingAllocator.numBlocksInUse());
assert(1 == objectCountingAllocator.numBlocksTotal());
assert(0 == defaultCountingAllocator.numBlocksInUse());
assert(1 == defaultCountingAllocator.numBlocksTotal());
-
- Example 2: Detecting Allocator Propagation Bugs:
- This example demonstrates how the default allocator is used to detect a very common programming error pertaining to allocator usage. First we define the trivial (but buggy)
my_IdPair
class:
#include <my_id.h>
#include <bslma_default.h>
class my_IdPair {
my_Id d_id;
my_Id d_alias;
my_IdPair(const my_IdPair&);
my_IdPair& operator=(const my_IdPair&);
public:
my_IdPair(const char *id,
const char *alias,
bslma::Allocator *basicAllocator = 0);
~my_IdPair();
const char *id() const;
const char *alias() const;
};
inline
my_IdPair::my_IdPair(const char *id,
const char *alias,
bslma::Allocator *basicAllocator)
: d_id(id, bslma::Default::allocator(basicAllocator))
, d_alias(alias)
{
}
inline
my_IdPair::~my_IdPair()
{
}
inline
const char *my_IdPair::id() const
{
return d_id.id();
}
inline
const char *my_IdPair::alias() const
{
return d_alias.id();
}
The definition of the my_IdPair
constructor above intentionally includes a common programming error: The allocator in use by the object is not passed to all data members that require it. We will see shortly how this error is detected at runtime using the default allocator.
- Next, the default allocator is set and locked identically to what was done in usage example 1: Now we instantiate an object of type
my_IdPair
without explicitly specifying an allocator. As a result, the object uses the default allocator. The assertions verify the expected changes in the state of the default allocator: assert(0 == defaultCountingAllocator.numBlocksInUse());
assert(0 == defaultCountingAllocator.numBlocksTotal());
{
my_IdPair idPair("A", "B");
assert(2 == defaultCountingAllocator.numBlocksInUse());
assert(2 == defaultCountingAllocator.numBlocksTotal());
}
assert(0 == defaultCountingAllocator.numBlocksInUse());
assert(2 == defaultCountingAllocator.numBlocksTotal());
Next we instantiate a second object of type my_IdPair
, this time supplying it with a counting allocator object that is distinct from the default allocator. The assertions in the following code fragment that are commented out indicate the expected states of the allocators (i.e., in a bug-free implementation of my_IdPair
) after the object has been constructed and again after it has been destroyed. However, due to the (intentional) bug in the constructor, the uncommented assertions reveal the true state of affairs: my_CountingAllocator objectCountingAllocator;
assert(0 == objectCountingAllocator.numBlocksInUse());
assert(0 == objectCountingAllocator.numBlocksTotal());
{
my_IdPair idPair("X", "Y", &objectCountingAllocator);
assert(1 == objectCountingAllocator.numBlocksInUse());
assert(1 == objectCountingAllocator.numBlocksTotal());
assert(1 == defaultCountingAllocator.numBlocksInUse());
assert(3 == defaultCountingAllocator.numBlocksTotal());
}
assert(0 == objectCountingAllocator.numBlocksInUse());
assert(1 == objectCountingAllocator.numBlocksTotal());
assert(0 == defaultCountingAllocator.numBlocksInUse());
assert(3 == defaultCountingAllocator.numBlocksTotal());
Note that, although not necessary in the case of the simple my_IdPair
class, the default allocator can be used (and typically should be used) within the body of a constructor, or any other member function, to allocate dynamic memory that is temporarily needed by the method (and, hence, not owned by the object after the method has returned). Thus, the invariant that must hold immediately after a method of an object returns is that the value returned by defaultCountingAllocator.numBlocksInUse()
must be identical to what it was immediately prior to calling the method. Of course, note that the above invariant pertains to cases in single-threaded programs where the object allocator in use by the object is distinct from the default allocator. Also note that the value returned by defaultCountingAllocator.numBlocksTotal()
can differ across function invocations (i.e., even in correct code).
-
- Example 3: Basic Global Allocator Use:
- Next we define a simple singleton class,
my_Singleton
, that defaults to using the global allocator if one is not explicitly specified when the singleton object is initialized. Toward that end, note that in contrast to my_Id
, the constructor for my_Singleton
uses: in its member initializer:
class my_Singleton {
static my_Singleton *s_singleton_p;
my_Id d_id;
my_Singleton(const my_Singleton& original,
bslma::Allocator *basicAllocator = 0);
my_Singleton& operator=(const my_Singleton& rhs);
private:
explicit my_Singleton(const char *id,
bslma::Allocator *basicAllocator = 0);
~my_Singleton();
public:
static void initSingleton(const char *id,
bslma::Allocator *basicAllocator = 0);
static const my_Singleton& singleton();
const char *id() const;
};
inline
const my_Singleton& my_Singleton::singleton()
{
return *s_singleton_p;
}
inline
my_Singleton::my_Singleton(const char *id,
bslma::Allocator *basicAllocator)
: d_id(id, bslma::Default::globalAllocator(basicAllocator))
{
}
inline
my_Singleton::~my_Singleton()
{
}
inline
const char *my_Singleton::id() const
{
return d_id.id();
}
The following completes the definition of my_Singleton
in the component .cpp
file: In the following, the default and global allocators are set to distinct instances of my_CountingAllocator
. Note that the default allocator is set and locked identically to what was done in the previous two usage examples: Finally, we initialize the singleton object. We explicitly specify the desired allocator in the call to initSingleton
to make our intentions as clear as possible. Of course, because of the way the my_Singleton
constructor was written, the result would have been the same if no allocator had been specified. As in previous examples, the states of the default and global allocators are asserted before and after initializing the singleton: assert(0 == defaultCountingAllocator.numBlocksInUse());
assert(0 == defaultCountingAllocator.numBlocksTotal());
assert(0 == globalCountingAllocator.numBlocksInUse());
assert(0 == globalCountingAllocator.numBlocksTotal());
my_Singleton::initSingleton("S", bslma::Default::globalAllocator());
assert(0 == defaultCountingAllocator.numBlocksInUse());
assert(0 == defaultCountingAllocator.numBlocksTotal());
assert(1 == globalCountingAllocator.numBlocksInUse());
assert(1 == globalCountingAllocator.numBlocksTotal());