How is allocator-aware container assignment implemented?

前端 未结 2 1090
清歌不尽
清歌不尽 2021-02-10 15:56

For example, from std::deque::operator = in C++ Reference:
(1) Copy Assignment (const std::deque &other)

2条回答
  •  灰色年华
    2021-02-10 16:25

    I am answering my own question to show what I got. --Dannyu NDos, 2017 Jan 16

    Either in the copy or move assignment, its behavior depends on two conditions:
    1. is the allocators compare equal? (That is, is the source allocator able to destroy and deallocate the target container's elements?)
    2. does the source's allocator propagate (= be assigned to target) during container assignment?

    For copy assignment:
    A. If the allocators compare equal:
    Directly copy-assigning elements to elements can be safely done.
    As the allocators already compare equal, it doesn't matter whether the allocator propagates. If any element needs to be constructed or destroyed, it also doesn't matter whose allocator does it.
    B. If the allocators don't compare equal:
    B.a. If the allocator doesn't propagate:
    Directly copy-assigning elements to elements can be safely done, but if any element needs to be constructed or destroyed, the source allocator must do it, as only it can destroy target container's elements.
    B.b. If the allocator propagates:
    First, the target allocator must destory and deallocate all the target container's elements.
    And then the allocator propagates, and then the source allocator allocates and copy-constructs all the source container's elements.

    For move assignment:
    A. If the allocators compare equal:
    The target container erases all its elements, and then takes ownership of the source container's elements. This takes O(1) time.
    B. If the allocators don't compare equal:
    B.a. If the allocator doesn't propagate:
    Directly move-assigning elements to elements can be safely done, but if any element needs to be constructed or destroyed, the source allocator must do it, as only it can destroy source container's element. This takes O(n) time. The source container must be in valid state after assignment.
    B.b. If the allocator propagates:
    First, the target allocator must destory and deallocate all the target container's elements.
    And then the allocator propagates, and then the source allocator allocates and move-constructs all the source container's elements. This takes O(n) time. The source container must be in valid state after assignment.

    In source code, given alloc is container's allocator, Alloc is its type, they are generally written like this:

    /*container*/ &operator = (const /*container*/ &other) {
        if (std::allocator_traits::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::is_always_equal::value) {
        if (alloc == other.alloc) {
            clear();
            // *this takes ownership of other's elements.
        } else if (std::allocator_traits::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;
    }
    

提交回复
热议问题