Как реализуется назначение контейнеров с учетом распределителя?

Например, из std :: deque :: operator = в C ++ Справочник:
(1) Копировать назначение (const std :: deque & other) < / strong>

Заменяет содержимое копией содержимого другого.
Если std :: allocator_traits ::ropate_on_container_copy_assignment () истинно, целевой распределитель заменяется копией исходного распределителя. Если целевой и исходный распределители не сравниваются одинаково, целевой (* 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 после этого находится в допустимом, но неуказанном состоянии. Если std :: allocator_traits ::ropate_on_container_move_assignment () истинно, целевой распределитель заменяется копией исходного распределителя. Если оно ложно и исходный и целевой распределители не сравниваются равными, цель не может стать владельцем исходной памяти и должна перемещать-назначать каждый элемент индивидуально, выделяя дополнительную память, используя свой собственный распределитель по мере необходимости. В любом случае, все элементы, изначально присутствующие в * this, либо уничтожаются, либо заменяются поэлементным перемещением-присваиванием.

Если this->get_allocator() == other.get_allocator(), это простая задача.
Но если нет, следуют те же вопросы, приведенные выше, за исключением этого случая, когда используется назначение перемещения.

В обоих случаях у меня есть дополнительный вопрос.
Если элементы нельзя ни копировать, ни перемещать, можно ли уничтожить их и построить из других? Если да, то чей распределитель я должен использовать?


person Dannyu NDos    schedule 25.11.2016    source источник


Ответы (2)


Распределитель POCCA (распространение-на-контейнере-копии-назначении) назначается копией как часть назначения копии контейнера. Аналогичным образом, распределитель 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(), поскольку вы ничего не распространяете.

В обоих случаях у меня есть дополнительный вопрос. Если элементы нельзя ни копировать, ни перемещать, можно ли их уничтожить и построить из других? Если да, то чей распределитель я должен использовать?

Уничтожьте его, используя распределитель, с которым он был изначально построен; создайте его, используя распределитель, с которым он будет уничтожен. Другими словами, если вы распространяете распределитель, уничтожьте его с помощью целевого распределителя и создайте с помощью исходного распределителя.

person T.C.    schedule 25.11.2016
comment
А как насчет распределителей, не относящихся к POCCA? Должен ли я использовать this->get_allocator() в любое время? - person Dannyu NDos; 25.11.2016
comment
Ну да, если нужно выделить. - person T.C.; 25.11.2016

Я отвечаю на свой вопрос, чтобы показать, что у меня есть. --Dannyu NDos, 16 января 2017 г.

При назначении копирования или перемещения его поведение зависит от двух условий:
1. равны ли распределители при сравнении? (То есть может ли исходный распределитель уничтожать и освобождать элементы целевого контейнера?)
2. Распространяется ли исходный распределитель (= назначается целевому) во время назначения контейнера?

Для присвоения копий:
A. Если распределители сравнивают равные:
Прямое копирование-присвоение элементов элементам может быть безопасно выполнено.
Поскольку распределители уже сравнивают равные, это не так. t имеет значение, распространяется ли распределитель. Если какой-либо элемент необходимо создать или уничтожить, также не имеет значения, чей распределитель это делает.
B. Если распределители не сравнивают равные:
Ba < / strong> Если распределитель не распространяется:
Прямое копирование элементов с присваиванием элементам может быть безопасно выполнено, но если какой-либо элемент должен быть создан или уничтожен, исходный распределитель должен это сделать, так как только он может уничтожить цель элементов контейнера.
Bb Если распределитель распространяется:
Сначала целевой распределитель должен уничтожить и освободить все элементы целевого контейнера.
Затем распределитель распространяет, а затем источник распределитель выделяет и копирует-конструирует все элементы исходного контейнера.

Для присвоения перемещения:
A. Если распределители сравниваются одинаково:
Целевой контейнер стирает все свои элементы, а затем становится владельцем элементов исходного контейнера. Это занимает время O (1).
Б. Если распределители не сравниваются равными:
Ba Если распределитель не распространяется:
Непосредственное присваивание элементов перемещению к элементам может быть безопасно выполнено, но если какой-либо элемент необходимо создать или уничтожить, это должен сделать исходный распределитель, так как только он может уничтожить элемент исходного контейнера. Это занимает O (n) времени. После назначения исходный контейнер должен находиться в допустимом состоянии.
Bb Если распределитель распространяется:
Во-первых, целевой распределитель должен уничтожить и освободить все элементы целевого контейнера.
А затем распределитель распространяется, а затем распределитель источника выделяет и перемещает-конструирует все элементы исходного контейнера. Это занимает 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;
}
person Dannyu NDos    schedule 16.01.2017