Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why allow `propagate_on_container_swap == false` in Allocators, when it might cause undefined behaviour?

Note: Originally asked by Matt Mcnabb as a comment on Why can swapping standard library containers be problematic in C++11 (involving allocators)?.


The Standard (N3797) says that if progagate_on_container_swap inside an Allocator is std::false_type it will yield undefined behaviour if the two allocators involved doesn't compare equal.

  • Why would the Standard allow such construct when it seems more than dangerous?

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().

like image 717
Filip Roséen - refp Avatar asked May 20 '14 11:05

Filip Roséen - refp


1 Answers

I can think of a few real-life scenarios where the construct allowed by the Standard both makes sense, and is required, however; I'll first try to answer this question from a broader perspective, not involving any specific problem.


THE EXPLANATION

Allocators are this magical things responsible for allocating, constructing, destructing, and deallocating memory and entities. Since C++11 when stateful allocators came into play an allocator can do much more than previously, but it all boils down to the previously mentioned four operations.

Allocators have loads of requirements, one of them being that a1 == a2 (where a1 and a2 are allocators of the same type) must yield true only if memory allocated by one can be deallocated by the other [1].

The above requirement of operator== means that two allocators comparing equal can do things differently, as long as they still have a mutual understanding of how memory is allocated.

The above is why the Standard allows propagate_on_container_* to be equal to std::false_type; we might want to change the contents of two containers which allocators have the same deallocation behavior, but leave the other behavior (not related to basic memory management) behind.


[1] as stated in [allocator.requirements]p2 (table 28)


THE (SILLY) STORY

Imagine that we have an Allocator named Watericator, it gathers water upon requested allocation, and hands it to the requested container.

Watericator is a stateful Allocator, and upon constructing our instance we can choose two modes;

  1. employ Eric, who fetches water down at the fresh water spring, while also measures (and reports) water level and purity.

  2. employ Adam, who uses the tap out in the backyard and doesn't care anything about logging. Adam is a lot faster than Eric.


No matter where the water comes from we always dispose of it in the same way; by watering our plants. Even if we have one instance where Eric is supplying us water (memory), and another where Adam is using the tap, both Watericators compare equal as far as operator== is concerned.

Allocations done by one can be deallocated by the other.


The above might be a silly similie, but imagine we have an allocator which does logging upon every allocation, and we uses this on a container somewhere in our code that interests us; we later want to move the elements out from this container into another one.. but we are no longer interested in all that logging.

Without stateful allocators, and the option to turn propagate_on_container_* off, we would be forced to either 1) copy every element involved 2) be stuck with that (no longer required) logging.

like image 82
Filip Roséen - refp Avatar answered Sep 23 '22 01:09

Filip Roséen - refp