注意: Matt Mcnabb最初要求comment上的Why can swapping standard library containers be problematic in C++11 (involving allocators)?。
标准(N3797)表示如果分配器中的progagate_on_container_swap
为std::false_type
,则会产生未定义的行为涉及的两个分配器不相等。
23.2.1p9
一般容器要求[container.requirements.general]
如果
allocator_traits<allocator_type>::propagate_on_container_swap::value
是true
,然后还应交换a
和b
的分配器 使用对非成员swap
的未命名调用。 否则,他们应该 不得交换,除非a.get_allocator() == b.get_allocator()
,否则行为未定义。
答案 0 :(得分:10)
我可以想到一些现实生活中的场景,其中标准所允许的构造既有意义,又是必需的;我将首先尝试从更广泛的角度回答这个问题,而不涉及任何具体问题。
说明
分配器是负责分配,构造,破坏和释放内存和实体的神奇事物。自从C ++ 11 有状态分配器发挥作用以来,分配器可以比以前做得更多,但这一切都归结为前面提到的四个操作。
分配器有大量需求,其中一个是a1 == a2
(其中a1
和a2
是相同类型的分配器)必须产生{{ 1}} 仅如果分配的内存可以被其他 [1] 解除分配。
true
的上述要求意味着两个比较相等的分配器可以做不同的事情,只要它们仍然可以相互理解内存的分配方式。
以上是标准允许operator==
等于propagate_on_container_*
的原因;我们可能想要更改两个容器的内容,其中分配器具有相同的释放行为,但保留其他行为(与基本内存管理无关)。
std::false_type
中所述的[1] (表28)
(SILLY)故事
想象一下,我们有一个名为 Watericator 的分配器,它会根据请求的分配收集水,并将其交给所请求的容器。
Watericator 是一个有状态的分配器,在构造我们的实例时我们可以选择两种模式;
雇用 Eric ,他们在淡水泉水中取水,同时测量(并报告)水位和纯度。
雇用 Adam ,他在后院使用挖掘机,并且不关心任何有关日志记录的事情。 Adam 比 Eric 快很多。
无论水来自哪里,我们总是以同样的方式处理它;浇水我们的植物。即使我们有一个实例 Eric 为我们提供水(内存),另一个实例[allocator.requirements]p2
正在使用水龙头,两个 Watericators 比较等于关注Adam
。
一方完成的分配可以由另一方解除分配。
以上可能是一个愚蠢的比喻,但想象一下我们有一个分配器可以记录每个分配,我们在代码中的某个容器上使用它,这对我们感兴趣;我们后来想把元素从这个容器移到另一个容器中......但是我们不再对所有的日志记录感兴趣了。
如果没有有状态的分配器,以及关闭operator==
的选项,我们将被迫1)复制所涉及的每个元素2)坚持使用(不再需要)记录。
答案 1 :(得分:0)
标准允许propagate_on_container_swap
导致未定义行为,而标准通过此值公开未定义行为并非如此!
一个简单的例子是考虑一个作用域分配器,它从本地池分配内存,当分配器超出范围时删除所述池:
template <typename T>
class scoped_allocator;
现在,让我们使用它:
int main() {
using scoped = scoped_allocator<int>;
scoped outer_alloc;
std::vector<int, scoped> outer{outer_alloc};
outer.push_back(3);
{
scoped inner_alloc;
std::vector<int, scoped> inner{inner_alloc};
inner.push_back(5);
swap(outer, inner); // Undefined Behavior: loading...
}
// inner_allocator is dead, but "outer" refers to its memory
}