Quick Links: |
Provide a uniform interface to standard allocator types. More...
Namespaces | |
namespace | bslma |
namespace | bsl |
bsl::allocator_traits | Uniform interface to standard allocator types |
allocator_traits
class template is defined in the C++11 standard ([allocator.traits]) as a uniform mechanism for accessing nested types within, and operations on, any standard-conforming allocator. An allocator_traits
specialization is stateless, and all of its member functions are static. In most cases, facilities of allocator_traits
are straight pass-throughs for the same facilities from the ALLOC
template parameter. For example, allocator_traits<X>pointer
is the same as X::pointer
and allocator_traits<X>allocate(x, n)
is the same as x.allocate(n)
. The advantage of using allocator_traits
instead of directly using the allocator is that the allocator_traits
interface can supply parts of the interface that are missing from ALLOC
. In fact, the most important purpose of allocator_traits
is to provide implementations of C++11 allocator features that were absent in C++03, thus allowing a C++03 allocator to work with C++11 containers. allocator_traits
, but constrains the set of allocator types on which it may be instantiated. Specifically, this implementation does not provide defaults for C++03 types and functions, and has hard-wired implementations of the new C++11 features. Thus, the allocator_traits
template cannot be instantiated on an allocator type that does not provide a full compliment of types and functions required by the C++03 standard, and it will ignore any special C++11 features specified in ALLOC
. This limitation exists because Bloomberg does not need the full functionality of the C++11 model, but needs only to distinguish between C++03 allocators and allocators that implement the BSLMA allocator model (see bslma_stdallocator
). The full feature set of allocator_traits
would require a lot of resources for implementation and (especially) testing. Moreover, a full implementation would require metaprogramming that is too advanced for the feature set of the compilers currently in use at Bloomberg. This interface is useful, however, as a way to future-proof containers against the eventual implementation of the full feature set, and to take advantage of the Bloomberg-specific features described below. allocator_traits
interface: the construct
function having a variable-length argument list (limited to 5 constructor arguments on compilers that don't support variadic templates) and the allocator-propagation traits. The implementations of these features within this component are tuned to Bloomberg's needs. The construct
member function will automatically forward the allocator to the constructed object iff the ALLOC
parameter is convertible from bslma::Allocator*
and the object being constructed has the bslma::UsesBslmaAllocator
type trait, as per standard Bloomberg practice. The select_on_container_copy_construction
static member will return a default-constructed allocator iff ALLOC
is convertible from bslma::Allocator *
because bslma allocators should not be copied when a container is copy-constructed; otherwise this function will return a copy of the allocator, as per C++03 container rules. The other propagation traits all have a false
value, so allocators are not propagated on assignment or swap. construct
and destroy
methods of the parameterized allocator type will not be called. Rather, the target object will always be constructed at the address specified by the user, by calling the constructor in-place. Similarly, the destructor will always be called directly, rather than using a parameterized allocator's destroy
method. Otherwise, this implementation will fully support the C++03 model, including use of allocators returning "smart pointers" from allocate
. allocator_traits
to implement a standard-conforming container class. First, we create a container class that holds a single object and which meets the requirements both of a standard container and of a Bloomberg container. I.e., when instantiated with an allocator argument it uses the standard allocator model; otherwise it uses the bslma
model. We provide an alias, AllocTraits
, to the specific allocator_traits
instantiation to simplify the implementation of each method that must allocate memory, or create or destroy elements. #include <bslma_allocatortraits.h> using namespace BloombergLP; template <class TYPE, class ALLOC = bsl::allocator<TYPE> > class MyContainer { // This class provides a container that always holds exactly one // element, dynamically allocated using the specified allocator. typedef bsl::allocator_traits<ALLOC> AllocTraits; // Alias for the 'allocator_traits' instantiation to use for all // memory management requests. // DATA ALLOC d_allocator; TYPE *d_value_p; public: typedef TYPE value_type; typedef ALLOC allocator_type; // etc. // CREATORS explicit MyContainer(const ALLOC& a = ALLOC()); explicit MyContainer(const TYPE& v, const ALLOC& a = ALLOC()); MyContainer(const MyContainer& other); MyContainer(const MyContainer& other, const ALLOC& a); ~MyContainer(); // MANIPULATORS ALLOC get_allocator() const { return d_allocator; } // ACCESSORS TYPE& front() { return *d_value_p; } const TYPE& front() const { return *d_value_p; } // etc.
MyContainer
so that it is recognized as an STL sequence container: bslma
allocators if the ALLOC
template parameter is convertible from bslma::Allocator*
. // TRAITS // We would do the following if 'bslalg' was accessible. // BSLMF_NESTED_TRAIT_DECLARATION( // MyContainer, bslalg::HasStlIterators); BSLMF_NESTED_TRAIT_DECLARATION_IF( MyContainer, bslmf::IsBitwiseMoveable, bslmf::IsBitwiseMoveable<ALLOC>::value); BSLMF_NESTED_TRAIT_DECLARATION_IF( MyContainer, bslma::UsesBslmaAllocator, (bsl::is_convertible<bslma::Allocator*, ALLOC>::value)); };
TYPE
object in the allocated memory. Because the allocation and construction are done in two separate steps, we need to create a proctor that will deallocate the allocated memory in case the constructor throws an exception. The proctor uses the uniform interface provided by allocator_traits
to access the pointer
and deallocate
members of ALLOC
: template <class ALLOC> class MyContainerProctor { // This class implements a proctor to release memory allocated during // the construction of a 'MyContainer' object if the constructor for // the container's data element throws an exception. Such a proctor // should be 'release'd once the element is safely constructed. typedef typename bsl::allocator_traits<ALLOC>::pointer pointer; ALLOC d_alloc; pointer d_data_p; public: MyContainerProctor(const ALLOC& a, pointer p) : d_alloc(a), d_data_p(p) { } ~MyContainerProctor() { if (d_data_p) { bsl::allocator_traits<ALLOC>::deallocate(d_alloc, d_data_p, 1); } } void release() { d_data_p = pointer(); } };
allocate
and construct
members of allocator_traits
, which provide the correct semantic for passing the allocator to the constructed object when appropriate: template <class TYPE, class ALLOC> MyContainer<TYPE, ALLOC>::MyContainer(const ALLOC& a) : d_allocator(a) { d_value_p = AllocTraits::allocate(d_allocator, 1); MyContainerProctor<ALLOC> proctor(a, d_value_p); // Call 'construct' with no constructor arguments AllocTraits::construct(d_allocator, d_value_p); proctor.release(); } template <class TYPE, class ALLOC> MyContainer<TYPE, ALLOC>::MyContainer(const TYPE& v, const ALLOC& a) : d_allocator(a) { d_value_p = AllocTraits::allocate(d_allocator, 1); MyContainerProctor<ALLOC> proctor(a, d_value_p); // Call 'construct' with one constructor argument of type 'TYPE' AllocTraits::construct(d_allocator, d_value_p, v); proctor.release(); }
MyContainer
needs to conditionally copy the allocator from the other
container. The copy constructor uses allocator_traits::select_on_container_copy_construction
to decide whether to copy the other
allocator (for non-bslma allocators) or to default-construct the allocator (for bslma allocators). template <class TYPE, class ALLOC> MyContainer<TYPE, ALLOC>::MyContainer(const MyContainer& other) : d_allocator(bsl::allocator_traits<ALLOC>:: select_on_container_copy_construction(other.d_allocator)) { d_value_p = AllocTraits::allocate(d_allocator, 1); MyContainerProctor<ALLOC> proctor(d_allocator, d_value_p); AllocTraits::construct(d_allocator, d_value_p, *other.d_value_p); proctor.release(); }
allocator_traits
functions to destroy and deallocate the value object: template <class TYPE, class ALLOC> MyContainer<TYPE, ALLOC>::~MyContainer() { AllocTraits::destroy(d_allocator, d_value_p); AllocTraits::deallocate(d_allocator, d_value_p, 1); }
MyContainer
, instantiating it with element type int
: int usageExample1() { bslma::TestAllocator testAlloc; MyContainer<int> C1(123, &testAlloc); assert(C1.get_allocator() == bsl::allocator<int>(&testAlloc)); assert(C1.front() == 123); MyContainer<int> C2(C1); assert(C2.get_allocator() == bsl::allocator<int>()); assert(C2.front() == 123); return 0; }
MyContainer
is instantiated with a C++03 allocator, that the allocator is a) copied on copy construction and b) is not propagated from the container to its elements. Firstly we create a representative element class, MyType
, that allocates memory using the bslma allocator protocol: #include <bslma_default.h> class MyType { bslma::Allocator *d_allocator_p; // etc. public: // TRAITS BSLMF_NESTED_TRAIT_DECLARATION(MyType, bslma::UsesBslmaAllocator); // CREATORS explicit MyType(bslma::Allocator* basicAlloc = 0) : d_allocator_p(bslma::Default::allocator(basicAlloc)) { /* ... */ } MyType(const MyType&) : d_allocator_p(bslma::Default::allocator(0)) { /* ... */ } MyType(const MyType&, bslma::Allocator* basicAlloc) : d_allocator_p(bslma::Default::allocator(basicAlloc)) { /* ... */ } // etc. // ACCESSORS bslma::Allocator *allocator() const { return d_allocator_p; } // etc. };
template <class TYPE> class MyCpp03Allocator { int d_state; public: typedef TYPE value_type; typedef TYPE *pointer; typedef const TYPE *const_pointer; typedef unsigned size_type; typedef int difference_type; template <class OTHER> struct rebind { typedef MyCpp03Allocator<OTHER> other; }; // CREATORS explicit MyCpp03Allocator(int state = 0) : d_state(state) { } // ALLOCATION FUNCTIONS TYPE* allocate(size_type n, const void* = 0) { return static_cast<TYPE *>(::operator new(sizeof(TYPE) * n)); } void deallocate(TYPE* p, size_type) { ::operator delete(p); } // ELEMENT CREATION FUNCTIONS template <class ELEMENT_TYPE> void construct(ELEMENT_TYPE *p) { ::new (static_cast<void *>(p)) ELEMENT_TYPE(); } template <class ELEMENT_TYPE, class A1> void construct(ELEMENT_TYPE *p, BSLS_COMPILERFEATURES_FORWARD_REF(A1) a1) { ::new (static_cast<void *>(p)) ELEMENT_TYPE(BSLS_COMPILERFEATURES_FORWARD(A1, a1)); } template <class ELEMENT_TYPE, class A1, class A2> void construct(ELEMENT_TYPE *p, BSLS_COMPILERFEATURES_FORWARD_REF(A1) a1, BSLS_COMPILERFEATURES_FORWARD_REF(A2) a2) { ::new (static_cast<void *>(p)) ELEMENT_TYPE(BSLS_COMPILERFEATURES_FORWARD(A1, a1), BSLS_COMPILERFEATURES_FORWARD(A2, a2)); } template <class ELEMENT_TYPE, class A1, class A2, class A3> void construct(ELEMENT_TYPE *p, BSLS_COMPILERFEATURES_FORWARD_REF(A1) a1, BSLS_COMPILERFEATURES_FORWARD_REF(A2) a2, BSLS_COMPILERFEATURES_FORWARD_REF(A3) a3) { ::new (static_cast<void *>(p)) ELEMENT_TYPE(BSLS_COMPILERFEATURES_FORWARD(A1, a1), BSLS_COMPILERFEATURES_FORWARD(A2, a2), BSLS_COMPILERFEATURES_FORWARD(A3, a3)); } template <class ELEMENT_TYPE, class A1, class A2, class A3, class A4> void construct(ELEMENT_TYPE *p, BSLS_COMPILERFEATURES_FORWARD_REF(A1) a1, BSLS_COMPILERFEATURES_FORWARD_REF(A2) a2, BSLS_COMPILERFEATURES_FORWARD_REF(A3) a3, BSLS_COMPILERFEATURES_FORWARD_REF(A4) a4) { ::new (static_cast<void *>(p)) ELEMENT_TYPE(BSLS_COMPILERFEATURES_FORWARD(A1, a1), BSLS_COMPILERFEATURES_FORWARD(A2, a2), BSLS_COMPILERFEATURES_FORWARD(A3, a3), BSLS_COMPILERFEATURES_FORWARD(A4, a4)); } template <class ELEMENT_TYPE, class A1, class A2, class A3, class A4, class A5> void construct(ELEMENT_TYPE *p, BSLS_COMPILERFEATURES_FORWARD_REF(A1) a1, BSLS_COMPILERFEATURES_FORWARD_REF(A2) a2, BSLS_COMPILERFEATURES_FORWARD_REF(A3) a3, BSLS_COMPILERFEATURES_FORWARD_REF(A4) a4, BSLS_COMPILERFEATURES_FORWARD_REF(A5) a5) { ::new (static_cast<void *>(p)) ELEMENT_TYPE(BSLS_COMPILERFEATURES_FORWARD(A1, a1), BSLS_COMPILERFEATURES_FORWARD(A2, a2), BSLS_COMPILERFEATURES_FORWARD(A3, a3), BSLS_COMPILERFEATURES_FORWARD(A4, a4), BSLS_COMPILERFEATURES_FORWARD(A5, a5)); } template <class ELEMENT_TYPE> void destroy(ELEMENT_TYPE *p) { p->~ELEMENT_TYPE(); } // ACCESSORS static size_type max_size() { return UINT_MAX / sizeof(TYPE); } int state() const { return d_state; } }; template <class TYPE1, class TYPE2> inline bool operator==(const MyCpp03Allocator<TYPE1>& lhs, const MyCpp03Allocator<TYPE2>& rhs) { return lhs.state() == rhs.state(); } template <class TYPE1, class TYPE2> inline bool operator!=(const MyCpp03Allocator<TYPE1>& lhs, const MyCpp03Allocator<TYPE2>& rhs) { return ! (lhs == rhs); }
MyContainer
using this allocator type and verify that elements are constructed using the default allocator (because the allocator is not propagated from the container). We also verify that the allocator is copied on copy-construction: int usageExample2() { typedef MyCpp03Allocator<MyType> MyTypeAlloc; MyContainer<MyType, MyTypeAlloc> C1a(MyTypeAlloc(1)); assert((bsl::is_same<MyContainer<MyType, MyTypeAlloc>::allocator_type, MyTypeAlloc>::value)); assert(C1a.get_allocator() == MyTypeAlloc(1)); assert(C1a.front().allocator() == bslma::Default::defaultAllocator()); MyContainer<MyType, MyTypeAlloc> C2a(C1a); assert(C2a.get_allocator() == C1a.get_allocator()); assert(C2a.get_allocator() != MyTypeAlloc()); assert(C2a.front().allocator() == bslma::Default::defaultAllocator()); MyType dummy; MyContainer<MyType, MyTypeAlloc> C1b(dummy, MyTypeAlloc(1)); assert((bsl::is_same<MyContainer<MyType, MyTypeAlloc>::allocator_type, MyTypeAlloc>::value)); assert(C1b.get_allocator() == MyTypeAlloc(1)); assert(C1b.front().allocator() == bslma::Default::defaultAllocator()); MyContainer<MyType, MyTypeAlloc> C2b(C1b); assert(C2b.get_allocator() == C1b.get_allocator()); assert(C2b.get_allocator() != MyTypeAlloc()); assert(C2b.front().allocator() == bslma::Default::defaultAllocator()); return 0; }