1

I have read some old posts on propagate_on_container_move_assignment to understand how it works but still not able to wrap my head around some of the finer points. As per my understanding, move constructors work in a simple manner ( move construct allocator along with any internal state) but move assignment operators need to handle when allocators can propagate.

This answer in the following question two links provide some background on how the allocator propagation work and why it is required

Copied from the above links -

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.

I understand the first two, however, for the third case "propagate_on_container_move_assignment is false, and the allocators from the lhs and rhs compare unequal." I do not understand how is this not an issue for move constructor. In case 3 for move assignments, we end up copying data as we can not own the memory of rhs allocator. While during move construction, we assume we can always own the memory and just move construct the allocator along with the internal state. If this were possible in move constructor then couldn't we do similar in move assignment - move assign the allocator and move assign the internal state.

To me it looks like if case 3 is true for move assignment then that would mean our move constructors as implemented without the "propagate" checks would be ill-formed.

Please help me understand the concept. Best provide an example where for move assignment we go for 3rd case due to allocator not supporting propagation( there should be a valid reason for the allocator to not support pocma = std::true_type ) but move constructor works?

Thanks,

4
  • The constructor always installs an allocator (because it starts empty), so there is no option for source and target to be different. Commented Sep 9, 2023 at 13:53
  • I understand that. But if an allocator is not safe to move (as in # 3 case) then how come constructor does not care about it. Commented Sep 9, 2023 at 14:36
  • Can you give an example where we will go for case 3 in move assignment due to allocator not supporting propagation( there should be a valid reason for the allocator to not support pocma = std::true_type ) but move construction works? I will upadate the question to provide an example. Commented Sep 9, 2023 at 15:47
  • I have no examples, but have noted that a move constrcutor for an allocator has the very special requiement that the orignal is unchanged! There is no such requirement for move assignment, but you can instead select not to assign it. Commented Sep 9, 2023 at 17:43

2 Answers 2

2

When propagate_on_container_move_assignment is false, it does not imply that the allocator "is not safe to move". It implies that the author of the allocator has determined that a container that uses that allocator should not have its allocator changed by a move assignment. std::pmr::polymorphic_allocator was explicitly designed this way, so that when a container uses std::pmr::polymorphic_allocator as its allocator, then the allocator of the object can't be changed during the object's lifetime; the creator of the object has sole and absolute control over how that container allocates memory.

As a result of this design decision, the move assignment operator must account for the case where the LHS keeps its original allocator but that allocator is unequal to the RHS's allocator, meaning that the LHS allocator cannot deallocate the memory allocated by the RHS allocator. This means that, for example, in the case of std::vector's move assignment operator, the LHS cannot simply "steal" the buffer from the RHS because the result would be the LHS owning memory that the LHS's allocator does not know how to deallocate. Instead the move assignment would degenerate into an element-wise move assignment in this case.

This issue can't arise during move construction because allocators always propagate on move construction. There is no propagate_on_container_move_construction customization point that can change this behaviour. So the new object gets its allocator from the old object and, therefore, that allocator always knows how to deallocate the buffer from the old object that the new object is now taking ownership of.

This situation also can't arise during copy construction because a copy constructor never adopts memory from the source object so it doesn't need to worry about whether the new object's allocator can deallocate any adopted memory. First, the copy constructor determines the allocator that the new object will use; then, it uses that allocator to allocate memory for all the elements in the new object. Clearly, that same allocator will know how to deallocate that memory later.

Sign up to request clarification or add additional context in comments.

Comments

0

C++98 does not require allocators to be assignable (and indeed there are allocators that do not provide operator=). For backward compatibility, the library cannot use the assignment operator unless the allocator says it's possible to do so.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.