为什么在分配器中允许`propagate_on_container_swap == false`,何时可能导致未定义的行为?

Fil*_*efp 11 c++ allocator language-lawyer c++11

注:最初由问马特·麦克纳布评论为什么能交换标准库容器在C++ 11(包括分配器)是有问题的?.


标准(N3797)说,如果progagate_on_container_swap内部分配器std::false_type它会产生不确定的行为,如果涉及的两个分配器不比较平等的.

  • 为什么标准允许这样的结构似乎不仅仅是危险的?

23.2.1p9 一般集装箱要求 [container.requirements.general]

如果 allocator_traits<allocator_type>::propagate_on_container_swap::valuetrue,那么也应该使用对非成员的未经授权的呼叫来交换a和的分配器.否则,它们不应被交换,并且行为是未定义的,除非.bswapa.get_allocator() == b.get_allocator()

Fil*_*efp 10

我可以想到一些现实生活中的场景,其中标准所允许的构造既有意义,又是必需的; 我将首先尝试从更广泛的角度回答这个问题,而不是涉及任何具体问题.


说明

分配器 是负责分配,构造,破坏和解除分配内存和实体的神奇事物.由于C++ 11 有状态分配器 发挥作用,分配器可以比以前做得更多,但这一切都归结为前面提到的四个操作.

分配器具有的要求载荷,其中之一是该a1 == a2(其中a1a2是相同的类型的分配器)必须产生true 如果内存分配由一个可被解除分配由另一[1] .

上述要求operator==意味着两个分配器比较相等可以做不同的事情,只要它们仍然可以相互理解内存的分配方式.

以上是标准允许propagate_on_container_*等于的原因std::false_type; 我们可能想要更改两个容器的内容,其中分配器具有相同的释放行为,但保留其他行为(与基本内存管理无关).


[1][allocator.requirements]p2(表28)所述


(SILLY)故事

想象一下,我们有一个名为Watericator分配器,它根据请求的分配收集水,并将其交给所请求的容器.

Watericator是一个有状态的分配器,在构建我们的实例时我们可以选择两种模式;

  1. 聘请埃里克,他在淡水泉水中取水,同时还测量(并报告)水位和纯度.

  2. 聘请亚当,他在后院使用水龙头,并不关心伐木.亚当是不是快了很多埃里克.


无论水来自哪里,我们总是以同样的方式处理它; 浇水我们的植物.即使我们有一个实例,埃里克正在为我们提供水(记忆),另一个Adam实际使用水龙头,两个水刀都比较平等operator==.

一个人完成的分配可以由另一个人解除分配.


上面可能是一个愚蠢的比喻,但想象一下我们有一个分配器可以记录每个分配,我们在我们的代码中的某个容器上使用它,这对我们感兴趣; 我们后来想把元素从这个容器移到另一个容器中......但我们不再对所有的日志记录感兴趣.

如果没有有状态分配器,并且propagate_on_container_*关闭选项,我们将被迫1)复制所涉及的每个元素2)坚持使用(不再需要)日志记录.