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

时间:2014-05-20 11:20:32

标签: c++ c++11 language-lawyer allocator

注意: Matt Mcnabb最初要求comment上的Why can swapping standard library containers be problematic in C++11 (involving allocators)?


标准(N3797)表示如果分配器中的progagate_on_container_swapstd::false_type,则会产生未定义的行为涉及的两个分配器不相等。

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

  

23.2.1p9 一般容器要求 [container.requirements.general]

     
    

如果     allocator_traits<allocator_type>::propagate_on_container_swap::value     是true,然后还应交换ab的分配器     使用对非成员swap的未命名调用。 否则,他们应该     不得交换,除非a.get_allocator() == b.get_allocator() ,否则行为未定义。

  

2 个答案:

答案 0 :(得分:10)

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


说明

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

分配器有大量需求,其中一个是a1 == a2(其中a1a2是相同类型的分配器)必须产生{{ 1}} 如果分配的内存可以被其他 [1] 解除分配

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

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


std::false_type中所述的

[1] (表28)


(SILLY)故事

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

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

  1. 雇用 Eric ,他们在淡水泉水中取水,同时测量(并报告)水位和纯度。

  2. 雇用 Adam ,他在后院使用挖掘机,并且不关心任何有关日志记录的事情。 Adam Eric 快很多。


  3. 无论水来自哪里,我们总是以同样的方式处理它;浇水我们的植物。即使我们有一个实例 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
}