I'm developing a container-like class and I would like to make use of the standard allocator infrastructure much like the standard containers. On the net I find a lot of material about how to use the std::allocator
class alone, or how to define a custom allocator for standard containers, but the material about how to make generically use of an standard conforming allocator is very rare, in particular in the context of C++11, where things seem to be much easier from the point of view of who writes a custom allocator, but more complex from the container's point of view.
So my question is about how to correctly make use of a standard conforming allocator in the most generic way, specifically:
I'm specifically interested in an answer about the C++11 world (does it change anything in C++14?)
In all answers below, I'm assuming you want to follow the rules for C++11 std-defined containers. The standard does not require you to write your custom containers this way.
- First of all, when should I design a custom container in this way? Is there a sensible performance overhead (including missing optimization opportunities) in using the default allocator instead of plain new/delete?
One of the most common and effective uses for custom allocators is to have it allocate off of the stack, for performance reasons. If your custom container can not accept such an allocator, then your clients will not be able to perform such an optimization.
- Do I have to explicitly call contained objects' destructors?
You have to explicitly call allocator_traits<allocator_type>::destroy(alloc, ptr)
, which in turn will either directly call the value_type
's destructor, or will call the destroy
member of the allocator_type
.
- How do I discriminate between stateful and stateless allocators?
I would not bother. Just assume the allocator is stateful.
- How to handle stateful allocators?
Follow the rules laid out in C++11 very carefully. Especially those for allocator-aware containers specified in [container.requirements.general]. The rules are too numerous to list here. However I'm happy to answer specific questions on any of those rules. But step one is get a copy of the standard, and read it, at least the container requirements sections. I recommend the latest C++14 working draft for this purpose.
- When (if ever) are two instances interchangeable (when can I destroy with one instance the memory allocated with another one)?
If two allocators compare equal, then either can deallocate pointers allocated from the other. Copies (either by copy construction or copy assignment) are required to compare equal.
- They have to be copied when the container is copied?
Search the standard for propagate_on
and select_on_container_copy_construction
for the nitty gritty details. The nutshell answer is "it depends."
- They can/have to be moved when the container is moved?
Have to be for move construction. Move assignment depends on propagate_on_container_move_assignment
.
- In the container's move constructor and move assignment operator, when can I move the pointer to allocated memory, and when do I have to allocate different memory and move the elements instead?
The newly move constructed container should have gotten its allocator by move constructing the rhs's allocator. These two allocators are required to compare equal. So you can transfer memory ownership for all allocated memory for which your container has a valid state for that pointer being nullptr
in the rhs.
The move assignment operator is arguably the most complicated: The behavior depends on propagate_on_container_move_assignment
and whether or not the two allocators compare equal. A more complete description is below in my "allocator cheat sheet."
- Are there issues about exception safety in this context?
Yes, tons. [allocator.requirements] lists the allocator requirements, which the container can depend on. This includes which operations can and can not throw.
You will also need to deal with the possibility that the allocator's pointer
is not actually a value_type*
. [allocator.requirements] is also the place to look for these details.
Good luck. This is not a beginner project. If you have more specific questions, post them to SO. To get started, go straight to the standard. I am not aware of any other authoritative source on the subject.
Here is a cheat-sheet I made for myself which describes allocator behavior, and the container's special members. It is written in English, not standard-eze. If you find any discrepancies between my cheat sheet, and the C++14 working draft, trust the working draft. One known discrepancy is that I've added noexcept
specs in ways the standard has not.
Allocator behavior:
C() noexcept(is_nothrow_default_constructible<allocator_type>::value); C(const C& c);
Gets allocator from
alloc_traits::select_on_container_copy_construction(c)
.C(const C& c, const allocator_type& a);
Gets allocator from
a
.C(C&& c) noexcept(is_nothrow_move_constructible<allocator_type>::value && ...);
Gets allocator from
move(c.get_allocator())
, transfers resources.C(C&& c, const allocator_type& a);
Gets allocator from
a
. Transfers resources ifa == c.get_allocator()
. Move constructs from eachc[i]
ifa != c.get_allocator()
.C& operator=(const C& c);
If
alloc_traits::propagate_on_container_copy_assignment::value
istrue
, copy assigns allocators. In this case, if allocators are not equal prior to assignment, dumps all resources from*this
.C& operator=(C&& c) noexcept( allocator_type::propagate_on_container_move_assignment::value && is_nothrow_move_assignable<allocator_type>::value);
If
alloc_traits::propagate_on_container_move_assignment::value
istrue
, dumps resources, move assigns allocators, and transfers resources fromc
.If
alloc_traits::propagate_on_container_move_assignment::value
isfalse
andget_allocator() == c.get_allocator()
, dumps resources, and transfers resources fromc
.If
alloc_traits::propagate_on_container_move_assignment::value
isfalse
andget_allocator() != c.get_allocator()
, move assigns eachc[i]
.void swap(C& c) noexcept(!allocator_type::propagate_on_container_swap::value || __is_nothrow_swappable<allocator_type>::value);
If
alloc_traits::propagate_on_container_swap::value
istrue
, swaps allocators. Regardless, swaps resources. Undefined behavior if the allocators are unequal andpropagate_on_container_swap::value
isfalse
.
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