Dannyu NDos Dannyu NDos - 14 days ago 5
C++ Question

How is allocator-aware container assignment implemented?

For example, from std::deque::operator = in C++ Reference:

(1) Copy Assignment (const std::deque &other)



Replaces the contents with a copy of the contents of other.
If
std::allocator_traits::propagate_on_container_copy_assignment() is
true, the target allocator is replaced by a copy of the source
allocator. If the target and the source allocators do not compare
equal, the target (*this) allocator is used to deallocate the memory,
then other's allocator is used to allocate it before copying the
elements.


If
this->get_allocator() == other.get_allocator()
, I can simply destroy and deallocate
this
' elements as needed, or allocate and construct elements as needed, or copy-assign the elements from
other
to
*this
as needed.

But what if not? Does the quote above mean that I can't copy-assign the elements, so I have to destroy and deallocate ALL the elements first, using
this->get_allocator()
, and then allocate and construct the elements, using
other.get_allocator()
? But if that is the case, why should I use
other.get_allocator
for the allocation? Won't it cause some runtime error later, as
this->get_allocator()
won't deallocate the memory properly?

(2) Move Assignment (std::deque &&other)



Replaces the contents with those of other
using move semantics (i.e. the data in other is moved from other into
this container). other is in a valid but unspecified state afterward.
If
std::allocator_traits::propagate_on_container_move_assignment()
is true, the target allocator is replaced by a copy of the source
allocator. If it is false and the source and the target allocators do
not compare equal, the target cannot take ownership of the source
memory and must move-assign each element individually, allocating
additional memory using its own allocator as needed. In any case, all
element originally present in *this are either destroyed or replaced
by elementwise move-assignment.


If
this->get_allocator() == other.get_allocator()
, this is an easy task. But if not, the same questions above follow, except in this case move-assignment is used.

In both cases, I have an additional question. If the elements can neither be copy-assigned or move-assigned, is it okay to destroy it and construct from other? If it is, whose allocator should I use?

Answer

A POCCA (propagate-on-container-copy-assignment) allocator is copy-assigned as part of the container's copy assignment. Likewise, a POCMA allocator is move-assigned when the container's move assigned.

Does the quote above mean that I can't copy-assign the elements, so I have to destroy and deallocate ALL the elements first, using this->get_allocator(), and then allocate and construct the elements, using other.get_allocator()?

Correct.

But if that is the case, why should I use other.get_allocator for the allocation? Won't it cause some runtime error later, as this->get_allocator() won't deallocate the memory properly?

Because the assignment propagates the allocator: after the assignment, this->get_allocator() is a copy of other.get_allocator(), so it can safely deallocate memory allocated by it.

If this->get_allocator() == other.get_allocator(), this is an easy task. But if not, the same questions above follow, except in this case move-assignment is used.

Actually, this is completely different. Move assignment with a POCMA allocator is trivial: you destroy all the elements in *this, free the memory, and plunder the memory and allocator of other.

The only case where container move assignment has to resort to element-wise move assignment/construction is when you have a non-POCMA allocator and the allocators compare unequal. In that case, all allocation and construction are done with this->get_allocator() since you don't propagate anything.

In both cases, I have an additional question. If the elements can neither be copy-assigned or move-assigned, is it okay to destroy it and construct from other? If it is, whose allocator should I use?

Destroy it using the allocator it was originally constructed with; construct it using the allocator it will be destroyed with. In other words, if you are propagating the allocator, then destroy it with the target allocator and construct with the source allocator.