注意: 最初要求的 马特麦克纳布 作为一个 评论 上 为什么在C ++ 11(涉及分配器)中交换标准库容器会有问题?。
标准 (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()
。
我可以想到一些现实生活中的场景,其中标准所允许的构造既有意义,又是必需的;我将首先尝试从更广泛的角度回答这个问题,而不是涉及任何具体问题。
说明
分配器 这些神奇的东西负责分配,构建,破坏和释放内存和实体。从C ++ 11开始 有状态分配器 发挥作用,分配器可以比以前做得更多,但这一切归结为前面提到的四个操作。
分配器 有很多要求,其中之一是 a1 == a2
(哪里 a1
和 a2
是相同类型的分配器)必须屈服 true
只要 如果记忆 分配 一个人可以 重新分配的 由另一个 [1]。
以上要求 operator==
意味着比较相等的两个分配器可以做不同的事情,只要它们仍然可以相互理解内存的分配方式。
以上是标准允许的原因 propagate_on_container_*
等于 std::false_type
;我们可能想要更改两个容器的内容,其中分配器具有相同的释放行为,但保留其他行为(与基本内存管理无关)。
[1] 如中所述 [allocator.requirements]p2
(表28)
(SILLY)故事
想象一下,我们有一个 分配器 命名 Watericator,它根据要求的分配收集水,并将其交给所请求的容器。
Watericator 是一个有状态的分配器,在构造我们的实例时我们可以选择两种模式;
采用 埃里克,他们在淡水泉水中取水,同时还测量(并报告)水位和纯度。
采用 亚当,谁在后院使用水龙头,并不关心伐木。 亚当 要快得多 埃里克。
无论水来自哪里我们 总是 以同样的方式处理它;浇水我们的植物。即使我们有一个实例在哪里 埃里克给我们供水(记忆),另一个在哪里 Adam
两者都在使用水龙头 Watericators 比较等于 operator==
被关注到。
一个人完成的分配可以由另一个分配。
上面可能是一个愚蠢的相似,但想象我们有一个分配器,它记录每个分配,我们在我们的代码中的某个容器上使用它,我们感兴趣;我们后来想把元素从这个容器移到另一个容器中......但我们不再对所有的日志记录感兴趣了。
没有有状态的分配器,也可以选择转向 propagate_on_container_*
关闭,我们将被迫1)复制所涉及的每个元素2)坚持(不再需要)记录。
标准允许的并不是那么多 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
}