【问题标题】:How is allocator-aware container assignment implemented?分配器感知容器分配是如何实现的?
【发布时间】:2017-04-09 15:49:26
【问题描述】:

例如,来自 C++ 参考中的 std::deque::operator =
(1) 复制赋值 (const std::deque &other)

用其他内容的副本替换内容。
如果 std::allocator_traits::propagate_on_container_copy_assignment() 是 true,目标分配器被源的副本替换 分配器。如果目标和源分配器不比较 相等,目标 (*this) 分配器用于释放内存, 然后其他的分配器用于在复制之前分配它 元素。

如果是this->get_allocator() == other.get_allocator(),我可以在需要时简单地销毁和释放this'元素,或者在需要时分配和构造元素,或者如果需要,将元素从other 复制分配到*this
但如果不是呢?上面的引用是否意味着我不能复制分配元素,所以我必须首先使用this->get_allocator() 销毁和释放所有元素,然后使用other.get_allocator() 分配和构造元素?
但是如果是这样的话,我为什么要使用other.get_allocator()进行分配呢?
以后不会导致一些运行时错误,因为this 不会正确释放内存吗?

(2) 移动赋值 (std::deque &&other)

将内容替换为其他内容 使用移动语义(即 other 中的数据从 other 移动到 这个容器)。 other 之后处于有效但未指定的状态。 如果 std::allocator_traits::propagate_on_container_move_assignment() 为真,目标分配器被源的副本替换 分配器。如果它是假的并且源和目标分配器做 不比较相等,目标不能获得源的所有权 内存并且必须单独移动分配每个元素,分配 根据需要使用自己的分配器来增加内存。无论如何,所有 最初出现在 *this 中的元素要么被破坏要么被替换 通过元素移动分配。

如果this->get_allocator() == other.get_allocator(),这很容易。
但如果不是,则与上述相同的问题,除了在这种情况下使用移动分配。

在这两种情况下,我还有一个问题。
如果元素既不能复制分配也不能移动分配,是否可以销毁它并从其他元素构造?如果是,我应该使用谁的分配器?

【问题讨论】:

    标签: c++ c++11 memory-management assign allocator


    【解决方案1】:

    POCCA(propagate-on-container-copy-assignment)分配器作为容器复制分配的一部分进行复制分配。同样,POCMA 分配器在分配容器的移动时被移动分配。

    上面的引用是否意味着我不能复制分配元素,所以我必须首先使用this->get_allocator()销毁和释放所有元素,然后使用other.get_allocator()分配和构造元素?

    正确。

    但如果是这样,我为什么要使用other.get_allocator 进行分配?以后不会导致一些运行时错误,因为this->get_allocator() 不会正确释放内存吗?

    因为赋值传播了分配器:在赋值之后,this->get_allocator()other.get_allocator() 的一个副本,所以它可以安全地释放它分配的内存。

    如果this->get_allocator() == other.get_allocator(),这很容易。但如果不是,则与上述相同的问题,除了在这种情况下使用移动分配。

    其实,这完全不同。使用 POCMA 分配器进行移动分配很简单:您销毁 *this 中的所有元素,释放内存,然后掠夺 other 的内存和分配器。

    容器移动分配必须诉诸元素移动分配/构造的唯一情况是当您有一个 非 POCMA 分配器并且分配器比较不相等时。在这种情况下,所有分配和构造都使用this->get_allocator() 完成,因为您不传播任何内容。

    在这两种情况下,我还有一个问题。如果元素既不能复制分配也不能移动分配,是否可以销毁它并从其他元素构造?如果是,我应该使用谁的分配器?

    使用最初构造它的分配器销毁它;使用将被销毁的分配器来构造它。换句话说,如果您正在传播分配器,则使用目标分配器将其销毁并使用源分配器进行构造。

    【讨论】:

    • 非 POCCA 分配器呢?我是否必须随时使用this->get_allocator()
    • 嗯,是的,如果你需要分配的话。
    【解决方案2】:

    我正在回答我自己的问题以显示我得到了什么。 --Dannyu NDos,2017 年 1 月 16 日

    在复制或移动分配中,其行为取决于两个条件:
    1.分配器是否比较相等? (也就是说,源分配器是否能够销毁和释放目标容器的元素?)
    2. 源分配器是否在容器分配期间传播(= 分配给目标)?

    对于复制分配:
    A.如果分配器比较相等:
    可以安全地直接将元素复制分配给元素。
    由于分配器已经比较相等,因此分配器是否传播并不重要。如果需要构造或销毁任何元素,那么由谁执行分配器也无关紧要。
    B. 如果分配器不比较相等:
    Ba 如果分配器不传播:
    直接将元素复制分配给元素是安全的,但如果需要构造或销毁任何元素,源分配器必须这样做,因为只有它才能销毁目标容器的元素。
    Bb如果分配器传播:
    首先,目标分配器必须销毁和释放目标容器的所有元素。
    然后分配器传播,然后源分配器分配并复制构造所有源容器的元素。

    对于移动分配:
    A.如果分配器比较相等:
    目标容器删除其所有元素,然后获得源容器元素的所有权。这需要 O(1) 时间。
    B. 如果分配器不比较相等:
    Ba 如果分配器不传播:
    可以安全地直接将元素移动分配给元素,但是如果需要构造或销毁任何元素,则源分配器必须这样做,因为只有它才能销毁源容器的元素。这需要 O(n) 时间。分配后源容器必须处于有效状态。
    B.b.如果分配器传播:
    首先,目标分配器必须销毁和释放目标容器的所有元素。
    然后分配器传播,然后源分配器分配并移动构造所有源容器的元素。这需要 O(n) 时间。分配后源容器必须处于有效状态。

    在源码中,给定alloc是容器的分配器,Alloc是它的类型,它们一般是这样写的:

    /*container*/ &operator = (const /*container*/ &other) {
        if (std::allocator_traits<Alloc>::propagate_on_container_copy_assignment::value && alloc != other.alloc) {
            clear();
            alloc = other.alloc;
            // directly copy-constructs the elements.
        } else {
            // directly copy-assigns the elements.
            // alloc does all allocation, construction, destruction, and deallocation as needed.
        }
        return *this;
    }
    /*container*/ &operator = (/*container*/ &&other) 
    noexcept(std::allocator_traits<Alloc>::is_always_equal::value) {
        if (alloc == other.alloc) {
            clear();
            // *this takes ownership of other's elements.
        } else if (std::allocator_traits<Alloc>::propagate_on_container_move_assignment::value) {
            clear();
            alloc = other.alloc;
            // directly move-constructs the elements.
        } else {
            // directly move-assigns the elements.
            // alloc does all allocation, construction, destruction, and deallocation as needed.
        }
        // the source container is made valid, if needed.
        return *this;
    }
    

    【讨论】:

      猜你喜欢
      • 2023-04-04
      • 1970-01-01
      • 2011-06-07
      • 2017-12-28
      • 2012-01-25
      • 1970-01-01
      • 1970-01-01
      • 2014-10-23
      • 1970-01-01
      相关资源
      最近更新 更多