分配器感知容器分配是如何实现的?

How is allocator-aware container assignment implemented?

例如,来自std::deque::operator = in C++ Reference:
(1) 复制赋值 (const std::deque &other)

Replaces the contents with a copy of the contents of other.
If std::allocator_traits::propagate_on_container_copy_assignment() is true, the target allocator is replaced by a copy of the source allocator. If the target and the source allocators do not compare equal, the target (*this) allocator is used to deallocate the memory, then other's allocator is used to allocate it before copying the elements.

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

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

Replaces the contents with those of other using move semantics (i.e. the data in other is moved from other into this container). other is in a valid but unspecified state afterward. If std::allocator_traits::propagate_on_container_move_assignment() is true, the target allocator is replaced by a copy of the source allocator. If it is false and the source and the target allocators do not compare equal, the target cannot take ownership of the source memory and must move-assign each element individually, allocating additional memory using its own allocator as needed. In any case, all element originally present in *this are either destroyed or replaced by elementwise move-assignment.

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

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

POCCA(传播容器复制分配)分配器作为容器复制分配的一部分进行复制分配。同样,当容器的移动被分配时,POCMA 分配器被移动分配。

Does the quote above mean that I can't copy-assign the elements, so I have to destroy and deallocate ALL the elements first, using this->get_allocator(), and then allocate and construct the elements, using other.get_allocator()?

正确。

But if that is the case, why should I use other.get_allocator for the allocation? Won't it cause some runtime error later, as this->get_allocator() won't deallocate the memory properly?

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

If this->get_allocator() == other.get_allocator(), this is an easy task. But if not, the same questions above follow, except in this case move-assignment is used.

实际上,这是完全不同的。使用 POCMA 分配器的移动赋值是微不足道的:您销毁 *this 中的所有元素,释放内存,并掠夺 other.

的内存和分配器

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

In both cases, I have an additional question. If the elements can neither be copy-assigned or move-assigned, is it okay to destroy it and construct from other? If it is, whose allocator should I use?

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

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

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

复制作业:
A. 如果分配器比较相等:
直接copy-assigning元素到元素就可以安全搞定了。
由于分配器已经比较相等,分配器是否传播并不重要。如果需要构造或销毁任何元素,由谁的分配器执行也无关紧要。
B. 如果分配器比较不相等:
B.a. 如果分配器不传播:
直接 copy-assigning 元素到元素可以安全地完成,但是如果需要构造或销毁任何元素,源分配器必须这样做,因为只有它可以销毁目标容器的元素。
B.b. 如果分配器传播:
首先,目标分配器必须销毁和释放所有目标容器的元素。
然后分配器传播,然后源分配器分配 copy-constructs 所有源容器的元素。

移动分配:
A. 如果分配器比较相等:
目标容器擦除其所有元素,然后取得源容器元素的所有权。这需要 O(1) 时间。
B. 如果分配器比较不相等:
B.a. 如果分配器不传播:
直接 move-assigning 元素到元素可以安全地完成,但是如果需要构造或销毁任何元素,源分配器必须这样做,因为只有它可以销毁源容器的元素。这需要 O(n) 时间。分配后源容器必须处于有效状态。
B.b. 如果分配器传播:
首先,目标分配器必须销毁和释放所有目标容器的元素。
然后分配器传播,然后源分配器分配 move-constructs 所有源容器的元素。这需要 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;
}