Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Example usage of propagate_on_container_move_assignment

Tags:

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          );       } } 
like image 673
Siler Avatar asked Dec 14 '14 15:12

Siler


1 Answers

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:

  1. propagate_on_container_move_assignment is true.
  2. propagate_on_container_move_assignment is false, and the allocators from the lhs and rhs compare equal.
  3. 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_types 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()); 
like image 180
Howard Hinnant Avatar answered Oct 04 '22 14:10

Howard Hinnant