Примерно използване на 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 конкретно говорят за присвояване... но какво да кажем за конструкторите? Трябва ли конструкторът за преместване или конструкторът за копиране на контейнер 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 е невярно и разпределителите от lhs и rhs се сравняват равни.
  3. 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_types do трябва да бъдат 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());
person Howard Hinnant    schedule 14.12.2014
comment
Какво ще кажете за случая, когато propagate_on_move_assignment е вярно, но разпределителите от lhs и rhs се сравняват неравномерно? Ако propagate_on_move_assignment е вярно, не трябва ли първо да проверим дали алокаторите са равни и ако са равни, не е необходимо да освобождаваме цялата памет, преди да присвоим преместването на алокаторите. (По този начин можем да използваме повторно вече разпределената памет) - person Siler; 16.12.2014
comment
Ако дизайнът на вашия контейнер е такъв, че можете да прехвърлите собствеността върху цялата памет от rhs към lhs, тогава няма нужда да използвате повторно каквато и да е памет на lhs. Ако обаче не можете да прехвърлите цялата памет, може да е удобно да използвате повторно паметта на lhs, ако тя не може да бъде доставена от rhs и ако разпределителите са равни. Моето пристрастие е, че дизайнът на контейнер е най-добър, когато 100% от паметта може да бъде прехвърлена, оставяйки rh в състояние без ресурси. - person Howard Hinnant; 16.12.2014
comment
От C++17, _ 1 _ може да се използва за разделяне на {1} и {2,3}. - person zjyhjqs; 28.10.2020