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