Пример использования 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;
}

Правильно ли это?

Кроме того, еще один источник путаницы заключается в том, что вложенные typedefs propagate_on_container_move/copy_assignment конкретно говорят о назначении... но что относительно конструкторов? Нужно ли проверять эти typedefs конструктор перемещения или конструктор копирования контейнера 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
         );
      }
}

Ответ 1

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

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

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

  • propagate_on_container_move_assignment истинно.
  • propagate_on_container_move_assignment является ложным, а распределители из lhs и rhs сравниваются равными.
  • propagate_on_container_move_assignment является ложным, а распределители из lhs и rhs сравниваются неравномерно.

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

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

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

Когда propagate_on_container_move_assignment является ложным, вы должны выбирать между случаями 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_type должны быть MoveAssignable и MoveInsertable (MoveConstructible), хотя они никогда не бывают, потому что вы выбираете между 2 и 3 во время выполнения. И 3 нуждается в этих операциях на value_type, чтобы сделать assign.

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

перемещать конструктор

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

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

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

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

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

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