I'm trying to understand how to properly write AllocatorAware containers.
My understandings is that the propagate_on_container_move_assignment
typedef indicates whether or not a certain Allocator
type needs to be copied when the Container itself is move-assigned.
So, since I can't find any examples of this, my own stab at it would be something like the following:
Given a container type Container
, an Allocator
type allocator_type
, and an internal allocator_type
data member m_alloc
:
Container& operator = (Container&& other) { if (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value) { m_alloc = std::allocator_traits<allocator_type>::select_on_container_copy_construction( other.m_alloc ); } return *this; }
Is this correct?
Also, another source of confusion here is that the nested typedefs propagate_on_container_move/copy_assignment
are specifically talking about assignment... but what about constructors? Does a move constructor or copy constructor of an AllocatorAware
container also need to check these typedefs ? I would think the answer would be yes here..., meaning, I'd also need to write:
Container(Container&& other) { if (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value) { m_alloc = std::allocator_traits<allocator_type>::select_on_container_copy_construction( other.m_alloc ); } }
I recommend studying the <vector>
header of libc++. You'll have to deal with all of the nasty underscores std::lib implementors are required to use. But libc++ has a C++11-conforming implementation, there for the inspecting.
move assignment operator
The container move assignment operator must deal with three separate possibilities:
propagate_on_container_move_assignment
is true.propagate_on_container_move_assignment
is false, and the allocators from the lhs and rhs compare equal.propagate_on_container_move_assignment
is false, and the allocators from the lhs and rhs compare unequal.When possible, the decision between these three cases should be made at compile time, not run time. Specifically, one should choose between the sets {1} and {2, 3} at compile time, since propagate_on_container_move_assignment
is a compile time constant. Compile-time branching on a compile-time constant is often done with tag dispatching, instead of with an if-statement as you show.
In none of these cases should select_on_container_copy_construction
be used. That function is only for the container copy constructor.
In case 1, the lhs should first use the lhs's allocator to deallocate all memory that has been allocated. This must be done first because the rhs allocator may not be able to deallocate this memory later. Then the lhs allocator is move-assigned from the rhs allocator (just like any other move assignment). Then memory ownership is transferred from the rhs container to the lhs container. If the design of your container is such that the rhs container can not be left in a resource-less state (a poor design imho), then a new resource can be allocated by the moved-from rhs allocator for the rhs container.
When propagate_on_container_move_assignment
is false, you must choose between cases 2 and 3 at run time, since the allocator comparison is a run time operation.
In case 2, you can do the same thing as in case 1, except do not move assign the allocators. Just skip that step.
In case 3, you can not transfer ownership of any memory from the rhs container to the lhs container. The only thing you can do is as if:
assign(make_move_iterator(rhs.begin()), make_move_iterator(rhs.end()));
Note that in case 1, because the algorithm has been chosen at compile-time, the value_type
of the container need not be MoveAssignable
nor MoveInsertable
(MoveConstructible
) to move-assign the container. But in case 2, the value_type
s do have to be MoveAssignable
and MoveInsertable
(MoveConstructible
), even though they never are, because you are choosing between 2 and 3 at run time. And 3 needs these operations on the value_type
to do the assign
.
The move assignment operator is easily the most complicated special member to implement for containers. The rest are much easier:
move constructor
The move constructor just move constructs the allocator and steals the resources from the rhs.
copy constructor
The copy constructor gets its allocator from select_on_container_copy_construction(rhs.m_alloc)
, and then uses that to allocate resources for the copy.
copy assignment operator
The copy assignment operator must first check if propagate_on_container_copy_assignment
is true. If it is, and if the lhs and rhs allocators compare unequal, then the lhs must first deallocate all memory, because it won't be able to do so later after the allocators are copy assigned. Next, if propagate_on_container_copy_assignment
, copy assign the allocators, else do not. Then copy the elements:
assign(rhs.begin(), rhs.end());
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