Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can swapping standard library containers be problematic in C++11 (involving allocators)?

Note: Originially asked by GreenScape as comment.


After reading Why are the swap member functions in STL containers not declared noexcept? it seems that the reason for potential undefined behavior when doing a.swap(b) for standard containers boils down to also swapping, or not swapping, the underlying allocators.

  • Why is swapping allocators along with data problematic?
like image 619
Filip Roséen - refp Avatar asked May 20 '14 10:05

Filip Roséen - refp


1 Answers

Let's start of by digging into the Standard (N3797):

23.2.1p9 General Container Requirements [container.requirements.general]

If allocator_traits<allocator_type>::propagate_on_container_swap::value is true, then the allocators of a and b shall also be exchanged using an unqalified call to non-member swap. Otherwise, they shall not be swapped, and the behavior is undefined unless a.get_allocator() == b.get_allocator().


What is the purpose of propagate_on_container_swap?

If an Allocator has a typedef named propagate_on_container_swap that refers to std::true_type the underlying Allocators of two containers being swapped, will also swap.[1]

If propagate_on_container_swap is std::false_type only the data of the two containers will swap, but the allocators will remain in their place.

[1] This means that after a.swap(b), a.get_allocator() will be that which was previously b.get_allocator(); the allocators has swapped.


What are the implications of stateful Allocators?

The Allocator is not only responsible for allocating memory for the elements within a Standard container, they are also responsible for the deallocation of said elements.

C++03 didn't allow stateful allocators within standard containers, but C++11 mandates that support for such must be present. This means that we could define an allocator that, depending on how it's constructed, acts in a certain way.

If the allocator has propagate_on_container_swap::value equal to false the difference in state between the two allocators involved might lead to undefined behavior, since one instance of the Allocator might not be compatible with the data handled by the other.


What might be the problem with stateful allocators if they are not swapped properly?

Let's say we have a MagicAllocator which either uses malloc or operator new to allocate memory, depending on how it's constructed.

If it uses malloc to allocate memory it must use free to deallocate it, and in case of operator new, delete is required; because of this it must maintain some information saying which of the two it should use.

If we have two std::vector which both uses MagicAllocator but with different states (meaning that one uses malloc and the other operator new), and we don't swap the allocators upon a.swap(b) the allocator won't match the memory allocated for the elements in the two vectors after the swap - which in terms means that the wrong free/delete might be called upon deallocation.

like image 149
Filip Roséen - refp Avatar answered Oct 19 '22 23:10

Filip Roséen - refp