BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bslma_managedptr

Detailed Description

Outline

Purpose

Provide a managed pointer class.

Classes

See also
bslmf_ispolymporphic

Description

This component provides a proctor, bslma::ManagedPtr, similar to bsl::auto_ptr, that supports user-specified deleters. The proctor is responsible for the automatic destruction of the object referenced by the managed pointer. As a "smart pointer", this object offers an interface similar to a native pointer, supporting dereference operators (*, ->), (in)equality comparison and testing as if it were a boolean value. However, like bsl::auto_ptr it has unusual "copy-semantics" that transfer ownership of the managed object, rather than making a copy. It should be noted that this signature does not satisfy the requirements for an element-type stored in any of the standard library containers. Note that this component will fail to compile when instantiated for a class that gives a false-positive for the type trait bslmf::IsPolymorphic. See the bslmf_ispolymporphic component for more details.

This component also provides the bslma::ManagedPtrUtil struct, which defines a namespace for utility functions that facilitate working with ManagedPtr objects. Of particular note are the allocateManaged and makeManaged class methods that can be used to create a managed object as well as a ManagedPtr to manage it, with the latter being returned. allocateManaged takes a bslma::Allocator * argument that is both (1) used to allocate the footprint of the managed object and (2) used by the managed object itself if it defines the bslma::UsesBslmaAllocator trait. makeManaged does not take a bslma::Allocator * argument and uses the default allocator to allocate the footprint of the managed object instead.

Factories

An object that will be managed by a ManagedPtr object is typically dynamically allocated and destroyed by a factory. For the purposes of this, component, a factory is any class that provides a deleteObject function taking a single argument of the (pointer) type of the managed pointer. The following is an example of a factory deleter:

class my_Factory {
// . . .
// MANIPULATORS
/// Create a `my_Type` object. Optionally specify a
/// `basicAllocator` used to supply memory. If `basicAllocator` is
/// 0, the currently installed default allocator is used.
my_Type *createObject(bslma::Allocator *basicAllocator = 0);
/// Delete the specified `object`.
void deleteObject(my_Type *object);
};
class my_Allocator : public bslma::Allocator { /* ... */ };
Definition bslma_allocator.h:457

Note that deleteObject is provided by all bslma allocators and by any object that implements the bdlma::Deleter protocol. Thus, any of these objects can be used as a factory deleter. The purpose of this design is to allow bslma allocators and factories to be used seamlessly as deleters.

Note that when the ManagedPtr(MANAGED_TYPE *) constructor is used, the managed object will be destroyed with a built-in deleter that calls delete ptr, but when the ManagedPtr(MANAGED_TYPE *, FACTORY_TYPE *) constructor is called with 0 == factory, the currently installed default allocator will be used as the factory.

Deleters

When a managed pointer is destroyed, the managed object is destroyed using the user supplied "deleter". A deleter is simply a function that is invoked with two void * arguments: a pointer to the object to be destroyed, and a pointer to a cookie that is supplied at the same time as the deleter and managed object.

typedef void (*DeleterFunc)(void *managedObject, void *cookie);

The meaning of the cookie depends on the specific deleter. Typically a deleter function will accept the two void * pointers and internally cast them to the appropriate types for pointers to the managed object and cookie. Note that there are no methods taking just a deleter, as the user must always supply a cookie to be passed when the deleter is actually invoked.

Note that this component still supports (deprecated) legacy deleters that expect to be passed pointers to the specific cookie and managed object types in use. This latter form of deleter was deprecated as it relies on undefined behavior, casting such function pointers to the correct form (taking two void * arguments) and invoking the function with two void * pointer arguments. While this is undefined behavior, it is known to have the desired effect on all platforms currently in use.

Aliasing

In a managed pointer, the pointer value (the value returned by the get method) and the pointer to the managed object need not have the same value. The loadAlias method allows a managed pointer to be created as an "alias" to another managed pointer (possibly of a different type), which we'll call the "original" managed pointer. When get is invoked on the alias, the aliased pointer value is returned, but when the managed pointer is destroyed, the original managed object will be passed to the deleter. (See also the documentation of the alias constructor or of the loadAlias method.)

Exception Safety

The principal usage of a managed pointer is to guarantee that a local object will be deallocated properly should an operation throw after its allocation. In this, it is very similar to bsl::auto_ptr. It is required for the proper functioning of this component that a deleter does not throw at invocation (upon destruction or re-assignment of the managed pointer).

Type Casting

ManagedPtr objects can be implicitly and explicitly cast to different types in the same way that native pointers can.

Explicit Casting

Through "aliasing", a managed pointer of any type can be explicitly cast to a managed pointer of any other type using any legal cast expression. See example 4 on type casting below for more details.

Implicit Casting

As with native pointers, a managed pointer of the type B that is derived from the type A, can be directly assigned to a ManagedPtr of A. Likewise a managed pointer of type B can be directly assigned to a ManagedPtr of const B. However, the rules for construction are a little more subtle, and apply when passing a bslma::ManagedPtr by value into a function, or returning as the result of a function.

class A {};
class B : public A {};
void test()
{
B *b_p = 0;
A *a_p = b_p;
bslma::ManagedPtr<A> a_mp1(b_mp1); // direct-initialization is valid
bslma::ManagedPtr<A> a_mp2 = b_mp1; // copy-initialization should fail
}
Definition bslma_managedptr.h:1182

Note that std::auto_ptr has the same restriction, and this failure will occur only on compilers that strictly conform to the C++ standard, such as recent gcc compilers or (in this case) IBM xlC.

Usage

In this section we show intended usage of this component.

Example 1: Implementing a Protocol

We demonstrate using ManagedPtr to configure and return a managed object implementing an abstract protocol.

First we define our protocol, Shape, a type of object that knows how to compute its area. Note that for expository reasons only, we do not give Shape a virtual destructor.

struct Shape {
/// Return the `area` of this shape.
virtual double area() const = 0;
};

Then we define a couple of classes that implement the Shape protocol, a Circle and a Square.

class Circle : public Shape {
private:
// DATA
double d_radius;
public:
// CREATORS
/// Create a `Circle` object having the specified `radius`.
explicit Circle(double radius);
/// Destroy this object.
virtual ~Circle();
// ACCESSORS
/// Return the area of this Circle, given by the formula pi*r*r.
virtual double area() const;
};
class Square : public Shape {
private:
// DATA
double d_sideLength;
public:
// CREATORS
/// Create a `Square` having sides of length `side`.
explicit Square(double side);
/// Destroy this object.
virtual ~Square();
// ACCESSORS
/// Return the area of this Square, given by the formula side*side
virtual double area() const;
};

Next we implement the methods for Circle and Square.

Circle::Circle(double radius)
: d_radius(radius)
{
}
Circle::~Circle()
{
}
double Circle::area() const {
return 3.141592653589793238462 * d_radius * d_radius;
}
Square::Square(double side)
: d_sideLength(side)
{
}
Square::~Square()
{
}
double Square::area() const {
return d_sideLength * d_sideLength;
}

Then we define an enumeration that lists each implementation of the Shape protocol.

struct Shapes {
enum VALUES { SHAPE_CIRCLE, SHAPE_SQUARE };
};

Now we can define a function that will return a Circle object or a Square object according to the specified kind parameter, and having its dimension specified by the caller.

bslma::ManagedPtr<Shape> makeShape(Shapes::VALUES kind, double dimension)
{
switch (kind) {
case Shapes::SHAPE_CIRCLE: {
Circle *circ = new(*alloc)Circle(dimension);
result.load(circ);
} break;
case Shapes::SHAPE_SQUARE: {
Square *sqr = new(*alloc)Square(dimension);
result.load(sqr);
} break;
}
return result;
}
static Allocator * defaultAllocator()
Definition bslma_default.h:889

Then, we can use our function to create shapes of different kinds, and check that they report the correct area. Note that we are using a radius of 1.0 for the Circle and integral side-length for the Square to support an accurate operator== with floating-point quantities. Also note that, despite the destructor for Shape being non-virtual, the correct destructor for the appropriate concrete Shape type is called. This is because the destructor is captured when the ManagedPtr constructor is called, and has access to the complete type of each shape object.

void testShapes()
{
bslma::ManagedPtr<Shape> shape = makeShape(Shapes::SHAPE_CIRCLE, 1.0);
assert(0 != shape);
assert(3.141592653589793238462 == shape->area());
shape = makeShape(Shapes::SHAPE_SQUARE, 2.0);
assert(0 != shape);
assert(4.0 == shape->area());
}

Next, we observe that as we are creating objects dynamically, we should pass an allocator to the makeShape function, rather than simply accepting the default allocator each time. Note that when we do this, we pass the user's allocator to the ManagedPtr object as the "factory".

bslma::ManagedPtr<Shape> makeShape(Shapes::VALUES kind,
double dimension,
bslma::Allocator *allocator)
{
switch (kind) {
case Shapes::SHAPE_CIRCLE: {
Circle *circ = new(*alloc)Circle(dimension);
result.load(circ, alloc);
} break;
case Shapes::SHAPE_SQUARE: {
Square *sqr = new(*alloc)Square(dimension);
result.load(sqr, alloc);
} break;
}
return result;
}
static Allocator * allocator(Allocator *basicAllocator=0)
Definition bslma_default.h:897

Finally we repeat the earlier test, additionally passing a test allocator:

void testShapesToo()
{
bslma::TestAllocator ta("object");
makeShape(Shapes::SHAPE_CIRCLE, 1.0, &ta);
assert(0 != shape);
assert(3.141592653589793238462 == shape->area());
shape = makeShape(Shapes::SHAPE_SQUARE, 3.0, &ta);
assert(0 != shape);
assert(9.0 == shape->area());
}
Definition bslma_testallocator.h:384

Example 2: Aliasing

Suppose that we wish to give access to an item in a temporary array via a pointer, which we will call the "finger". The finger is the only pointer to the array or any part of the array, but the entire array must be valid until the finger is destroyed, at which time the entire array must be deleted. We handle this situation by first creating a managed pointer to the entire array, then creating an alias of that pointer for the finger. The finger takes ownership of the array instance, and when the finger is destroyed, it is the array's address, rather than the finger, that is passed to the deleter.

First, let's say our array stores data acquired from a ticker plant accessible by a global getQuote function:

struct Ticker {
static double getQuote() // From ticker plant. Simulated here
{
static const double QUOTES[] = {
7.25, 12.25, 11.40, 12.00, 15.50, 16.25, 18.75, 20.25, 19.25, 21.00
};
static const int NUM_QUOTES = sizeof(QUOTES) / sizeof(QUOTES[0]);
static int index = 0;
double ret = QUOTES[index];
index = (index + 1) % NUM_QUOTES;
return ret;
}
};

Then, we want to find the first quote larger than a specified threshold, but would also like to keep the earlier and later quotes for possible examination. Our getFirstQuoteLargerThan function must allocate memory for an array of quotes (the threshold and its neighbors). It thus returns a managed pointer to the desired value:

const double END_QUOTE = -1;
getFirstQuoteLargerThan(double threshold, bslma::Allocator *allocator)
{
assert(END_QUOTE < 0 && 0 <= threshold);

Next, we allocate our array with extra room to mark the beginning and end with a special END_QUOTE value:

const int MAX_QUOTES = 100;
int numBytes = (MAX_QUOTES + 2) * sizeof(double);
double *quotes = (double *) allocator->allocate(numBytes);
quotes[0] = quotes[MAX_QUOTES + 1] = END_QUOTE;
virtual void * allocate(size_type size)=0

Then, we create a managed pointer to the entire array:

bslma::ManagedPtr<double> managedQuotes(quotes, allocator);

Next, we read quotes until the array is full, keeping track of the first quote that exceeds the threshold.

double *finger = 0;
for (int i = 1; i <= MAX_QUOTES; ++i) {
double quote = Ticker::getQuote();
quotes[i] = quote;
if (!finger && quote > threshold) {
finger = &quotes[i];
}
}

Now, we use the alias constructor to create a managed pointer that points to the desired value (the finger) but manages the entire array:

return bslma::ManagedPtr<double>(managedQuotes, finger);
}

Then, our main program calls getFirstQuoteLargerThan like this:

int aliasExample()
{
bslma::ManagedPtr<double> result = getFirstQuoteLargerThan(16.00, &ta);
assert(*result > 16.00);
assert(1 == ta.numBlocksInUse());
if (g_verbose) bsl::cout << "Found quote: " << *result << bsl::endl;
bsls::Types::Int64 numBlocksInUse() const
Definition bslma_testallocator.h:1087

Next, we also print the preceding 5 quotes in last-to-first order:

if (g_verbose) bsl::cout << "Preceded by:";
int i;
for (i = -1; i >= -5; --i) {
double quote = result.get()[i];
if (END_QUOTE == quote) {
break;
}
assert(quote < *result);
if (g_verbose) bsl::cout << ' ' << quote;
}
if (g_verbose) bsl::cout << bsl::endl;
TARGET_TYPE * get() const
Definition bslma_managedptr.h:2546

Then, to move the finger, e.g., to the last position printed, one must be careful to retain the ownership of the entire array. Using the statement result.load(result.get()-i) would be an error, because it would first compute the pointer value result.get()-i of the argument, then release the entire array before starting to manage what has now become an invalid pointer. Instead, result must retain its ownership to the entire array, which can be attained by:

result.loadAlias(result, result.get()-i);
void loadAlias(ManagedPtr< ALIASED_TYPE > &alias, TARGET_TYPE *ptr)
Definition bslma_managedptr.h:2437

Finally, if we reset the result pointer, the entire array is deallocated:

result.reset();
assert(0 == ta.numBlocksInUse());
assert(0 == ta.numBytesInUse());
return 0;
}
void reset()
Definition bslma_managedptr.h:2491
bsls::Types::Int64 numBytesInUse() const
Definition bslma_testallocator.h:1111

Example 3: Dynamic Objects and Factories

Suppose we want to track the number of objects currently managed by ManagedPtr objects.

First we define a factory type that holds an allocator and a usage-counter. Note that such a type cannot sensibly be copied, as the notion count becomes confused.

class CountedFactory {
// DATA
int d_count;
bslma::Allocator *d_allocator_p;
// NOT IMPLEMENTED
CountedFactory(const CountedFactory&);
CountedFactory& operator=(const CountedFactory&);
public:
// CREATORS
/// Create a `CountedFactory` object which uses the supplied
/// allocator `alloc`.
explicit CountedFactory(bslma::Allocator *alloc = 0);
/// Destroy this object.
~CountedFactory();

Next, we provide the createObject and deleteObject functions that are standard for factory objects. Note that the deleteObject function signature has the form required by bslma::ManagedPtr for a factory.

// MANIPULATORS
/// Return a pointer to a newly allocated object of type `TYPE`
/// created using its default constructor. Memory for the object
/// is supplied by the allocator supplied to this factory's
/// constructor, and the count of valid object is incremented.
template <class TYPE>
TYPE *createObject();
/// Destroy the object pointed to by `target` and reclaim the
/// memory. Decrement the count of currently valid objects.
template <class TYPE>
void deleteObject(const TYPE *target);

Then, we round out the class with the ability to query the count of currently allocated objects.

// ACCESSORS
int count() const;
// Return the number of currently valid objects allocated by this
// factory.
};

Next, we define the operations declared by the class.

CountedFactory::CountedFactory(bslma::Allocator *alloc)
: d_count(0)
, d_allocator_p(bslma::Default::allocator(alloc))
{
}
CountedFactory::~CountedFactory()
{
assert(0 == d_count);
}
template <class TYPE>
TYPE *CountedFactory::createObject()
{
TYPE *result = new(*d_allocator_p)TYPE;
++d_count;
return result;
}
template <class TYPE>
void CountedFactory::deleteObject(const TYPE *object)
{
d_allocator_p->deleteObject(object);
--d_count;
}
inline
int CountedFactory::count() const
{
return d_count;
}
Definition balxml_encoderoptions.h:68

Then, we can create a test function to illustrate how such a factory would be used with ManagedPtr.

void testCountedFactory()
{

Next, we declare a test allocator, and an object of our CountedFactory type using that allocator.

CountedFactory cf(&ta);

Then, we open a new local scope and declare an array of managed pointers. We need a local scope in order to observe the behavior of the destructors at end of the scope, and use an array as an easy way to count more than one object.

Next, we load each managed pointer in the array with a new int using our factory cf and assert that the factory count is correct after each new int is created.

int i = 0;
while (i != 4) {
pData[i++].load(cf.createObject<int>(), &cf);
assert(cf.count() == i);
}

Then, we reset the contents of a single managed pointer in the array, and assert that the factory count is appropriately reduced.

pData[1].reset();
assert(3 == cf.count());

Next, we load a managed pointer with another new int value, again using cf as the factory, and assert that the count of valid objects remains the same (destroy one object and add another).

pData[2].load(cf.createObject<int>(), &cf);
assert(3 == cf.count());
}

Finally, we allow the array of managed pointers to go out of scope and confirm that when all managed objects are destroyed, the factory count falls to zero, and does not overshoot.

assert(0 == cf.count());
}

Example 4: Type Casting

ManagedPtr objects can be implicitly and explicitly cast to different types in the same way that native pointers can.

Implicit Conversion

As with native pointers, a pointer of the type B that is publicly derived from the type A, can be directly assigned a ManagedPtr of A.

First, consider the following code snippets:

void implicitCastingExample()
{

If the statements:

bslma::TestAllocator localDefaultTa;
bslma::DefaultAllocatorGuard guard(&localDefaultTa);
int numdels = 0;
{
B *b_p = 0;
A *a_p = b_p;
Definition bslma_defaultallocatorguard.h:186

are legal expressions, then the statements

assert(!a_mp1 && !b_mp1);
a_mp1 = b_mp1; // conversion assignment of nil ptr to nil
assert(!a_mp1 && !b_mp1);
B *b_p2 = new (localDefaultTa) B(&numdels);
bslma::ManagedPtr<B> b_mp2(b_p2); // default allocator
assert(!a_mp1 && b_mp2);
a_mp1 = b_mp2; // conversion assignment of non-nil ptr to nil
assert(a_mp1 && !b_mp2);
B *b_p3 = new (localTa) B(&numdels);
bslma::ManagedPtr<B> b_mp3(b_p3, &localTa);
assert(a_mp1 && b_mp3);
a_mp1 = b_mp3; // conversion assignment of non-nil to non-nil
assert(a_mp1 && !b_mp3);
a_mp1 = b_mp3; // conversion assignment of nil to non-nil
assert(!a_mp1 && !b_mp3);
// constructor conversion init with nil
bslma::ManagedPtr<A> a_mp4(b_mp3, b_mp3.get());
assert(!a_mp4 && !b_mp3);
// constructor conversion init with non-nil
B *p_b5 = new (localTa) B(&numdels);
bslma::ManagedPtr<B> b_mp5(p_b5, &localTa);
bslma::ManagedPtr<A> a_mp5(b_mp5, b_mp5.get());
assert(a_mp5 && !b_mp5);
assert(a_mp5.get() == p_b5);
// constructor conversion init with non-nil
B *p_b6 = new (localTa) B(&numdels);
bslma::ManagedPtr<B> b_mp6(p_b6, &localTa);
bslma::ManagedPtr<A> a_mp6(b_mp6);
assert(a_mp6 && !b_mp6);
assert(a_mp6.get() == p_b6);
struct S {
int d_i[10];
};
assert(200 == numdels);
}
assert(400 == numdels);
} // implicitCastingExample()

Explicit Conversion

Through "aliasing", a managed pointer of any type can be explicitly converted to a managed pointer of any other type using any legal cast expression. For example, to static-cast a managed pointer of type A to a managed pointer of type B, one can simply do the following:

void explicitCastingExample() {
bslma::ManagedPtr<B> b_mp1(a_mp, static_cast<B *>(a_mp.get()));

or even use the less safe "C"-style casts:

bslma::ManagedPtr<B> b_mp2(a_mp, (B *)(a_mp.get()));
} // explicitCastingExample()

Note that when using dynamic cast, if the cast fails, the target managed pointer will be reset to an unset state, and the source will not be modified. Consider for example the following snippet of code:

void processPolymorphicObject(bslma::ManagedPtr<A> aPtr,
bool *castSucceeded)
{
bslma::ManagedPtr<B> bPtr(aPtr, dynamic_cast<B *>(aPtr.get()));
if (bPtr) {
assert(!aPtr);
*castSucceeded = true;
}
else {
assert(aPtr);
*castSucceeded = false;
}
}

If the value of aPtr can be dynamically cast to B * then ownership is transferred to bPtr; otherwise, aPtr is to be modified. As previously stated, the managed object will be destroyed correctly regardless of how it is cast.

Example 5: Inplace Object Creation

Suppose we want to allocate memory for an object, construct it in place, and obtain a managed pointer referring to this object. This can be done in one step using two free functions provided in bslma::ManagedPtrUtil.

First, we create a simple class clearly showing the features of these functions. Note that this class does not define the bslma::UsesBslmaAllocator trait. It is done intentionally for illustration purposes only, and definitely is not recommended in production code. The class has an elided interface (i.e., copy constructor and copy-assignment operator are not included for brevity):

/// Simple class that stores a copy of a null-terminated C-style string.
class String {
private:
// DATA
char *d_str_p; // stored value (owned)
bslma::Allocator *d_alloc_p; // allocator to allocate any dynamic
// memory (held, not owned)
public:
// CREATORS
/// Create an object having the same value as the specified `str`
/// using the specified `alloc` to supply memory.
String(const char *str, bslma::Allocator *alloc)
: d_alloc_p(alloc)
{
assert(str);
assert(alloc);
std::size_t length = std::strlen(str);
d_str_p = static_cast<char *>(d_alloc_p->allocate(length + 1));
std::strncpy(d_str_p, str, length + 1);
}
/// Destroy this object.
~String()
{
d_alloc_p->deallocate(d_str_p);
}
// ACCESSORS
/// Return a pointer providing modifiable access to the allocator
/// associated with this `String`.
bslma::Allocator *allocator() const
{
return d_alloc_p;
}
};
virtual void deallocate(void *address)=0

Next, we create a code fragment that will construct a managed String object using the default allocator to supply memory:

void testInplaceCreation()
{

Suppose we want to have a different allocator supply memory allocated by the object:

bsls::Types::Int64 testBytesInUse = ta.numBytesInUse();
assert(0 == testBytesInUse);
bsls::Types::Int64 defaultBytesInUse = da.numBytesInUse();
assert(0 == defaultBytesInUse);
long long Int64
Definition bsls_types.h:132

Then, create a string to copy:

const char *STR = "Test string";
const int STR_LENGTH = static_cast<int>(std::strlen(STR));

Next, dynamically create an object and obtain the managed pointer referring to it using the bslma::ManagedPtrUtil::makeManaged function:

{
bslma::ManagedPtr<String> stringManagedPtr =
bslma::ManagedPtrUtil::makeManaged<String>(STR, &ta);

Note that memory for the object itself is supplied by the default allocator, while memory for the copy of the passed string is supplied by another allocator:

assert(static_cast<int>(sizeof(String)) <= da.numBytesInUse());
assert(&ta == stringManagedPtr->allocator());
assert(STR_LENGTH + 1 == ta.numBytesInUse());
}

Then, make sure that all allocated memory is successfully released after managed pointer destruction:

assert(0 == da.numBytesInUse());
assert(0 == ta.numBytesInUse());

If you want to use an allocator other than the default allocator, then the allocateManaged function should be used instead:

bsls::Types::Int64 objectBytesInUse = oa.numBytesInUse();
assert(0 == objectBytesInUse);
{
bslma::ManagedPtr<String> stringManagedPtr =
bslma::ManagedPtrUtil::allocateManaged<String>(&oa, STR, &ta);
assert(static_cast<int>(sizeof(String)) <= oa.numBytesInUse());
assert(&ta == stringManagedPtr->allocator());
assert(STR_LENGTH + 1 == ta.numBytesInUse());
assert(0 == da.numBytesInUse());
}
assert(0 == da.numBytesInUse());
assert(0 == ta.numBytesInUse());
assert(0 == oa.numBytesInUse());
}

Next, let's look at a more common scenario where the object's type uses bslma allocators. In that case allocateManaged implicitly passes the supplied allocator to the object's constructor as an extra argument in the final position.

The second example class almost completely repeats the first one, except that it explicitly defines the bslma::UsesBslmaAllocator trait:

// Simple class that stores a copy of a null-terminated C-style string
// and explicitly claims to use `bslma` allocators.
class StringAlloc {
private:
// DATA
char *d_str_p; // stored value (owned)
bslma::Allocator *d_alloc_p; // allocator to allocate any dynamic
// memory (held, not owned)
public:
// TRAITS
// CREATORS
/// Create an object having the same value as the specified `str`.
/// Optionally specify a `basicAllocator` used to supply memory. If
/// `basicAllocator` is 0, the currently installed default allocator
/// is used.
StringAlloc(const char *str, bslma::Allocator *basicAllocator = 0)
: d_alloc_p(bslma::Default::allocator(basicAllocator))
{
assert(str);
std::size_t length = std::strlen(str);
d_str_p = static_cast<char *>(d_alloc_p->allocate(length + 1));
std::strncpy(d_str_p, str, length + 1);
}
/// Destroy this object.
~StringAlloc()
{
d_alloc_p->deallocate(d_str_p);
}
// ACCESSORS
/// Return a pointer providing modifiable access to the allocator
/// associated with this `StringAlloc`.
bslma::Allocator *allocator() const
{
return d_alloc_p;
}
};
#define BSLMF_NESTED_TRAIT_DECLARATION(t_TYPE, t_TRAIT)
Definition bslmf_nestedtraitdeclaration.h:231
Definition bslma_usesbslmaallocator.h:343

Then, let's create two managed objects using both makeManaged and allocateManaged:

void testUsesAllocatorInplaceCreation()
{
bsls::Types::Int64 testBytesInUse = ta.numBytesInUse();
assert(0 == testBytesInUse);
bsls::Types::Int64 defaultBytesInUse = da.numBytesInUse();
assert(0 == defaultBytesInUse);
const char *STR = "Test string";
const int STR_LENGTH = static_cast<int>(std::strlen(STR));

Note that we need to explicitly supply the allocator's address to makeManaged to be passed to the object's constructor:

{
bslma::ManagedPtr<StringAlloc> stringManagedPtr =
bslma::ManagedPtrUtil::makeManaged<StringAlloc>(STR, &ta);
assert(static_cast<int>(sizeof(String)) <= da.numBytesInUse());
assert(&ta == stringManagedPtr->allocator());
assert(STR_LENGTH + 1 == ta.numBytesInUse());
}

But the supplied allocator is implicitly passed to the constructor by allocateManaged:

{
bslma::ManagedPtr<StringAlloc> stringManagedPtr =
bslma::ManagedPtrUtil::allocateManaged<StringAlloc>(&ta, STR);
assert(static_cast<int>(sizeof(String)) + STR_LENGTH + 1 <=
assert(&ta == stringManagedPtr->allocator());
assert(0 == da.numBytesInUse());
}

Finally, make sure that all allocated memory is successfully released after the managed pointers (and the objects they manage) are destroyed:

assert(0 == da.numBytesInUse());
assert(0 == ta.numBytesInUse());
}