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

Detailed Description

Outline

Purpose

Provide an allocator interface for bsl::memory_resource objects.

Classes

Canonical header: bsl_memory_resource.h

See also
bslma_memoryresource, bslma_bslallocator

Description

This component provides an STL-compatible proxy for any resource class derived from bsl::memory_resource. The bsl::polymorphic_allocator interface is identical to that of std::pmr::polymorphic_allocator from the C++17 Standard Library; in fact, the former type is an alias for the latter type when using a C++17 or later library supplied by the platform.

The proxy class, bsl::polymorphic_allocator is a template that adheres to the allocator requirements defined in section [allocator.requirements] of the C++ standard. bsl::polymorphic_allocator may be used to instantiate any class template that is parameterized by a standard allocator. The container is expected to allocate memory for its own use through the allocator. A bsl::polymorphic_allocator object is initialized using a pointer to a resource object derived from bsl::memory_resource. Different types of memory resources use different allocation mechanisms, so this approach gives the programmer run time control over how the container obtains memory.

A container constructs its elements by calling the construct method on its allocator. Importantly, bsl::polymorphic_allocator is a scoped allocator – when its construct method is called, the allocator passes itself to the constructor of the object being constructed (if that object is allocator aware (AA) and uses a compatible the allocator type). Thus, a container instantiated with a scoped allocator ensures that its elements use the same allocator as the container itself.

A container using bsl::polymorphic_allocator should not copy its allocator on assignment and thus, to avoid errors, bsl::polymorphic_allocator, is not assignable. By design, a member of type bsl::polymorphic_allocator will prevent the client class from having a compiler-generated (defaulted) assignment operator because such an assignment operator would almost certainly do the wrong thing – copying the allocator and allocated objects instead of cloning those objects using the destination allocator. Once constructed, there is no straightforward way to rebind a bsl::polymorphic_allocator to use a different resource.

Instantiations of bsl::polymorphic_allocator have reference semantics. A bsl::polymorphic_allocator object does not "own" the bslma::Allocator with which it is initialized; copying a bsl::polymorphic_allocator object does not copy its resource object and destroying a bsl::polymorphic_allocator does not destroy its resource object. Two bsl::polymorphic_allocator objects compare equal if and only if the resource objects they refer to compare equal.

Thread Safety

Because it is immutable, non-assignable, and has reference semantics, a single bsl::polymorphic_allocator object is safe for concurrent access by multiple threads if and only if the bsl::memory_resource it references is safe for concurrent access from multiple threads. Separate objects of bsl::polymorphic_allocator type may safely be used in separate threads if and only if the bsl::memory_resource objects they reference are, themselves, safe for concurrent access.

Usage

This section illustrates intended use of this component.

Example 1: A class that allocates memory

In this example, we define a class template, Holder<TYPE>, that holds a single instance of TYPE on the heap. Holder is designed such that its memory use can be customized by supplying an appropriate allocator. A holder object can be empty and it can be move-constructed even if TYPE is not movable. In addition, the footprint of a Holder object is the same (typically the size of 2 pointers), regardless of the size of TYPE.

First, we create a CountingResource class, derived from bsl::memory_resource, that keeps track of the number of blocks of memory that were allocated from the resource but not yet returned to the resource; see usage example 1 in bslma_memoryresource .

#include <bsls_assert.h>
#include <bsls_keyword.h>
#include <stdint.h> // 'uintptr_t'
class CountingResource : public bsl::memory_resource {
// DATA
int d_blocksOutstanding;
CountingResource(const CountingResource&) BSLS_KEYWORD_DELETED;
CountingResource& operator=(const CountingResource&)
private:
// PRIVATE MANIPULATORS
void* do_allocate(std::size_t bytes,
std::size_t alignment) BSLS_KEYWORD_OVERRIDE;
void do_deallocate(void* p, std::size_t bytes,
std::size_t alignment) BSLS_KEYWORD_OVERRIDE;
// PRIVATE ACCESSORS
bool do_is_equal(const bsl::memory_resource& other) const
public:
// CREATORS
CountingResource() : d_blocksOutstanding(0) { }
~CountingResource() BSLS_KEYWORD_OVERRIDE;
// ACCESSORS
int blocksOutstanding() const { return d_blocksOutstanding; }
};
CountingResource::~CountingResource()
{
BSLS_assert(0 == d_blocksOutstanding);
}
void *CountingResource::do_allocate(std::size_t bytes,
std::size_t alignment)
{
void *ret = ::operator new(bytes);
if (uintptr_t(ret) & (alignment - 1)) {
::operator delete(ret);
BSLS_THROW(this); // Alignment failed
}
++d_blocksOutstanding;
return ret;
}
void CountingResource::do_deallocate(void* p, std::size_t, std::size_t)
{
::operator delete(p);
--d_blocksOutstanding;
}
bool CountingResource::do_is_equal(const bsl::memory_resource& other) const
{
return this == &other;
}
Definition bslma_memoryresource.h:441
memory_resource & operator=(const memory_resource &) BSLS_KEYWORD_DEFAULT
Return a modifiable reference to this object.
#define BSLS_THROW(X)
Definition bsls_exceptionutil.h:374
#define BSLS_KEYWORD_DELETED
Definition bsls_keyword.h:609
#define BSLS_KEYWORD_NOEXCEPT
Definition bsls_keyword.h:632
#define BSLS_KEYWORD_OVERRIDE
Definition bsls_keyword.h:653

Now we define our actual Holder template with with data members to hold the memory allocator and a pointer to the contained object:

template <class TYPE>
class Holder {
TYPE *d_data_p;
Definition bslma_polymorphicallocator.h:452

Next, we declare the constructors. Following the pattern for allocator-aware types used in BDE, the public interface contains an allocator_type typedef that can be passed to each constructor.:

public:
// TYPES
typedef bsl::polymorphic_allocator<TYPE> allocator_type;
// CREATORS
explicit Holder(const allocator_type& allocator = allocator_type());
explicit Holder(const TYPE& value,
const allocator_type& allocator = allocator_type());
Holder(const Holder& other,
const allocator_type& allocator = allocator_type());
Holder(bslmf::MovableRef<Holder> other); // IMPLICIT
const allocator_type& allocator);
~Holder();
Definition bslmf_movableref.h:751

Next, we declare the manipulators and accessors, allowing a Holder to be assigned and giving a client access to its value and allocator:

// MANIPULATORS
Holder& operator=(const Holder& rhs);
Holder& operator=(bslmf::MovableRef<Holder> rhs);
TYPE& value() { return *d_data_p; }
// ACCESSORS
bool isEmpty() const { return 0 == d_data_p; }
const TYPE& value() const { return *d_data_p; }
allocator_type get_allocator() const { return d_allocator; }
};

Next, we'll implement the first constructor, which creates an empty object; its only job is to store the allocator:

template <class TYPE>
Holder<TYPE>::Holder(const allocator_type& allocator)
: d_allocator(allocator)
, d_data_p(0)
{
}

Next, we'll implement the second constructor, which allocates memory and constructs an object in it. The try/catch block is needed to free the memory in case the constructor for TYPE throws and exception. An alternative implementation would use an RAII object to automatically free the memory in the case of an exception (see bslma_deallocatorproctor ):

template <class TYPE>
Holder<TYPE>::Holder(const TYPE& value, const allocator_type& allocator)
: d_allocator(allocator)
, d_data_p(0)
{
d_data_p = d_allocator.allocate(1);
::new(d_data_p) TYPE(value);
}
BSLS_CATCH(...) {
d_allocator.deallocate(d_data_p, 1);
}
}
BSLS_ANNOTATION_NODISCARD TYPE * allocate(std::size_t n)
Definition bslma_polymorphicallocator.h:937
void deallocate(TYPE *p, std::size_t n)
Definition bslma_polymorphicallocator.h:962
#define BSLS_CATCH(X)
Definition bsls_exceptionutil.h:372
#define BSLS_TRY
Definition bsls_exceptionutil.h:370
#define BSLS_RETHROW
Definition bsls_exceptionutil.h:378

Next, we'll implement a destructor that deletes the value object and deallocates the allocated memory:

template <class TYPE>
Holder<TYPE>::~Holder()
{
if (! isEmpty()) {
d_data_p->~TYPE(); // Destroy object.
d_allocator.deallocate(d_data_p, 1); // Deallocate memory.
}
}

Finally, we've implemented enough of Holder to demonstrate its use. Below, we pass the CountingResource from Example 1 to the constructors several Holder objects. Each non-empty Holder allocates one block of memory, which is reflected in the outstanding block count. Note that the address of the resource can be passed directly to the constructors because bsl::polymorphic_allocator is implicitly convertible from bsl::memory_resource *:

int main()
{
CountingResource rsrc;
{
Holder<int> h1(&rsrc); // Empty resource
assert(h1.isEmpty());
assert(0 == rsrc.blocksOutstanding());
Holder<int> h2(2, &rsrc);
assert(! h2.isEmpty());
assert(1 == rsrc.blocksOutstanding());
Holder<double> h3(3.0, &rsrc);
assert(! h3.isEmpty());
assert(2 == rsrc.blocksOutstanding());
}
assert(0 == rsrc.blocksOutstanding()); // Destructors freed memory
}