Пример использования propagate_on_container_move_assignment

Я пытаюсь понять, как правильно написать AllocatorAware контейнеры.

Насколько я понимаю, propagate_on_container_move_assignment typedef указывает, нужно ли копировать определенный тип Allocator, когда сам контейнер назначается для перемещения.

Итак, поскольку я не могу найти никаких примеров этого, мой собственный удар по этому поводу будет примерно следующим:

Учитывая тип контейнера Container, тип Allocator allocator_type и внутренний член данных allocator_type m_alloc:

Container& operator = (Container&& other)
{
  if (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
  {
     m_alloc = std::allocator_traits<allocator_type>::select_on_container_copy_construction(
      other.m_alloc
     );
  }

  return *this;
}

Это верно?

Кроме того, еще одним источником путаницы является то, что вложенные определения типов propagate_on_container_move/copy_assignment конкретно говорят о присваивании... но как насчет конструкторов? Нужно ли конструктору перемещения или конструктору копирования контейнера AllocatorAware также проверять эти определения типов? Я думаю, что здесь ответ будет да..., то есть мне также нужно будет написать:

Container(Container&& other)
{
      if (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
      {
         m_alloc = std::allocator_traits<allocator_type>::select_on_container_copy_construction(
          other.m_alloc
         );
      }
}

person Siler    schedule 14.12.2014    source источник


Ответы (1)


Я рекомендую изучить заголовок <vector> файла libc++. Вам придется иметь дело со всеми неприятными символами подчеркивания, которые должны использовать разработчики std::lib. Но у libc++ есть реализация, совместимая с C++11, для проверки.

оператор присваивания перемещения

Оператор назначения перемещения контейнера должен иметь дело с тремя отдельными возможностями:

  1. propagate_on_container_move_assignment верно.
  2. propagate_on_container_move_assignment ложно, и аллокаторы из левого и правого сравниваются равными.
  3. propagate_on_container_move_assignment ложно, а распределители из левого и правого сравниваются неравномерно.

Когда это возможно, решение между этими тремя случаями должно приниматься во время компиляции, а не во время выполнения. В частности, следует выбирать между наборами {1} и {2, 3} во время компиляции, поскольку propagate_on_container_move_assignment является константой времени компиляции. Ветвление во время компиляции для константы времени компиляции часто выполняется с помощью диспетчеризации тегов, а не с помощью оператора if, как вы шоу.

Ни в одном из этих случаев не следует использовать select_on_container_copy_construction. Эта функция предназначена только для конструктора копирования контейнера.

В случае 1 левая сторона должна сначала использовать аллокатор левой руки, чтобы освободить всю выделенную память. Это необходимо сделать в первую очередь, потому что аллокатор rhs не сможет освободить эту память позже. Затем левый распределитель назначается перемещением из правого распределителя (точно так же, как любое другое назначение перемещения). Затем право собственности на память передается из правого контейнера в левый. Если дизайн вашего контейнера таков, что контейнер rhs нельзя оставить в состоянии без ресурсов (плохой дизайн imho), то новый ресурс может быть выделен распределителем, перемещенным из rhs, для контейнера rhs.

Когда propagate_on_container_move_assignment равно false, вы должны выбирать между случаями 2 и 3 во время выполнения, поскольку сравнение распределителя является операцией времени выполнения.

В случае 2 вы можете сделать то же самое, что и в случае 1, за исключением того, что за исключением не перемещайте распределители. Просто пропустите этот шаг.

В случае 3 вы не можете передать право собственности на какую-либо память из контейнера rhs в контейнер lhs. Единственное, что вы можете сделать, это как бы:

assign(make_move_iterator(rhs.begin()), make_move_iterator(rhs.end()));

Обратите внимание, что в случае 1, поскольку алгоритм был выбран во время компиляции, value_type контейнера не обязательно должно быть MoveAssignable или MoveInsertable (MoveConstructible) для перемещения-назначения контейнера. Но в случае 2 value_types делать должны быть MoveAssignable и MoveInsertable (MoveConstructible), даже если они никогда не бывают таковыми, потому что вы выбираете между 2 и 3 во время выполнения. И 3 нужны эти операции на value_type для выполнения assign.

Оператор присваивания перемещения — самый сложный специальный член для реализации контейнеров. Остальные намного проще:

конструктор перемещения

Конструктор перемещения просто перемещает аллокатор и крадет ресурсы из rhs.

конструктор копирования

Конструктор копирования получает свой распределитель от select_on_container_copy_construction(rhs.m_alloc), а затем использует его для выделения ресурсов для копии.

оператор присваивания копии

Оператор присваивания копии должен сначала проверить, истинно ли propagate_on_container_copy_assignment. Если это так, и если левый и правый аллокаторы сравниваются неравномерно, то левый должен сначала освободить всю память, потому что он не сможет сделать это позже, после того, как аллокаторы будут назначены для копирования. Далее, если propagate_on_container_copy_assignment, скопируйте назначение распределителей, иначе не делайте этого. Затем скопируйте элементы:

assign(rhs.begin(), rhs.end());
person Howard Hinnant    schedule 14.12.2014
comment
Как насчет случая, когда propagate_on_move_assignment верно, но распределители из левого и правого каналов неравны? Если propagate_on_move_assignment истинно, не должны ли мы сначала проверить, равны ли аллокаторы по сравнению, и если они равны, нам не нужно освобождать всю память перед назначением перемещения аллокаторов. (Таким образом, мы можем повторно использовать уже выделенную память) - person Siler; 16.12.2014
comment
Если конструкция вашего контейнера такова, что вы можете передать право собственности на всю память с правого на левый, то нет необходимости повторно использовать какую-либо память на левом. Однако, если вы не можете передать всю память, может быть удобно повторно использовать память на левом, если она не может быть предоставлена ​​на правом, и если распределители равны. Мое предубеждение состоит в том, что дизайн контейнера лучше всего, когда можно передать 100% памяти, оставив правую часть в состоянии без ресурсов. - person Howard Hinnant; 16.12.2014
comment
Начиная с C++17, _ 1 _ можно использовать для разделения {1} и {2,3}. - person zjyhjqs; 28.10.2020