问题 为什么在Allocators中允许`propagate_on_container_swap == false`,它可能导致未定义的行为?


注意: 最初要求的 马特麦克纳布 作为一个 评论 上 为什么在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()


3044
2018-05-20 11:20


起源



答案:


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


说明

分配器  这些神奇的东西负责分配,构建,破坏和释放内存和实体。从C ++ 11开始 有状态分配器  发挥作用,分配器可以比以前做得更多,但这一切归结为前面提到的四个操作。

分配器 有很多要求,其中之一是 a1 == a2 (哪里 a1 和 a2 是相同类型的分配器)必须屈服 true  只要 如果记忆 分配 一个人可以 重新分配的 由另一个 [1]

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

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


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


(SILLY)故事

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

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

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

  2. 采用 亚当,谁在后院使用水龙头,并不关心伐木。 亚当 要快得多 埃里克


无论水来自哪里我们 总是 以同样的方式处理它;浇水我们的植物。即使我们有一个实例在哪里 埃里克给我们供水(记忆),另一个在哪里 Adam 两者都在使用水龙头 Watericators 比较等于 operator== 被关注到。

一个人完成的分配可以由另一个分配。


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

没有有状态的分配器,也可以选择转向 propagate_on_container_* 关闭,我们将被迫1)复制所涉及的每个元素2)坚持(不再需要)记录。


10
2018-05-20 11:20





标准允许的并不是那么多 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
}

0
2018-05-20 12:06