I would like to design a class template which takes an allocator type (as defined in Standard section 17.6.3.5) as a template argument. I see how std::allocator_traits<A>
helpfully fills in any missing members of A
with default settings. Beyond that, is there anything in the Standard Library or boost that would help use the allocator correctly?
In particular:
To honor the typedefs like std::allocator_traits<A>::propagate_on_container_copy_assignment
, do I have to check these things in the special member functions of each class which has a member of type A
? Or is there some wrapper type I could use as a member instead which would take care of this stuff?
If I want to overallocate to reduce the number of allocations by storing extra data next to the user-visible objects, is it appropriate to rebind the allocator something like this?
.
template<typename T, typename A>
class MyClass
{
private:
//...
struct storage {
int m_special_data;
T m_obj;
};
typedef typename std::allocator_traits<A>::template rebind_alloc<storage>
storage_alloc;
typedef typename std::allocator_traits<A>::template rebind_traits<storage>
storage_traits;
storage_alloc m_alloc;
static T* alloc(T&& obj)
{
storage_traits::pointer sp = storage_traits::allocate(m_alloc, 1);
sp->m_special_data = 69105;
return ::new(&sp->m_obj) T(std::move(obj));
}
//...
};
I don't know of anything to make life easier, allocator_traits
really makes it simpler to write an allocator, by providing all the boilerplate code, but it doesn't help use an allocator.
So that I could use a single allocator API in both C++03 and C++11 code I added <ext/alloc_traits.h>
to GCC 4.7, the class template __gnu_cxx::__alloc_traits
provides a consistent API that uses allocator_traits
in C++11 mode and calls the relevant member functions directly on the allocator in C++03 mode.
No, there's no wrapper or shortcut, the C++11 allocator requirements make a container author's job much more complicated. The requirements are slightly different for each container, depending on how it manages memory. For a vector-like type, in the copy-assignment operator if propagate_on_container_copy_assignment
(POCCA) is false and the existing capacity is greater than the source object's size then you can re-use the existing memory (if POCCA is true and the new allocator is not equal you can't re-use the old memory as it won't be possible to de-allocate it later after the allocator is replaced) but that optimization doesn't help much for a node-based container such as list or map.
That looks almost right, although you probably want to replace
return ::new(&sp->m_obj) T(std::move(obj));
with
A a(m_alloc);
std::allocator_traits<A>::construct(a, &sp->m_obj, std::move(obj));
return &sp->m_obj;
As stated in [container.requirements.general]/3 the containers which use an allocator use allocator_traits<A>::construct
to create the element type T
itself but any other types allocated (such as your storage
) must not use construct
.
If storage
itself is constructed then it will construct storage::m_obj
unless that member is a type which can be left uninitialized, such as std::aligned_storage<sizeof(T)>
, that can be initialized explicitly later by allocator_traits<A>::construct
. Alternatively, individually construct each member that needs non-trivial construction e.g. if storage
also had a string
member:
storage_traits::pointer sp = storage_traits::allocate(m_alloc, 1);
sp->m_special_data = 69105;
::new (&sp->m_str) std::string("foobar");
A a(m_alloc);
std::allocator_traits<A>::construct(a, &sp->m_obj, std::move(obj));
return &sp->m_obj;
The m_special_data
member is a trivial type so its lifetime begins as soon as storage is allocated for it. The m_str
and m_obj
members need non-trivial initialization so their lifetimes begin when their constructors complete, which is done by the placement new and the construct
call, respectively.
Edit: I've recently learnt that the standard has a defect (which I've reported) and the calls to construct
do not need to use a rebound allocator, so these lines:
A a(m_alloc);
std::allocator_traits<A>::construct(a, &sp->m_obj, std::move(obj));
can be replaced with:
std::allocator_traits<storage_alloc>::construct(m_alloc, &sp->m_obj, std::move(obj));
Which makes life slightly easier.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With