Каково обоснование для небезопасных операторов присваивания перемещений в стандартной библиотеке?

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

  • «обычный» («копируемый») договор переуступки в C ++ всегда считался безопасным от самопередачи; теперь у нас есть еще один несвязный угловой пример C ++, который нужно запомнить и объяснить — и немного опасный тоже; Я думаю, что мы все согласны с тем, что в C ++ необходимо не больше скрытых ловушек;
  • это усложняет алгоритмы — что-нибудь в remove_if семье нужно позаботиться об этом угловом деле;
  • было бы действительно легко выполнить это требование — там, где вы реализуете перемещение с помощью swap, оно предоставляется бесплатно, и даже в других случаях (когда вы можете получить некоторое повышение производительности с помощью специальной логики) это всего лишь один (почти) никогда не выполняемый ветка, которая практически бесплатна на любом процессоре¹; Кроме того, в большинстве интересных случаев (перемещения с использованием параметров или локальных параметров) ветвь будет полностью удаляться оптимизатором при встраивании (что должно происходить почти всегда для «простых» операторов назначения перемещения).

Итак, почему такое решение?


In Особенно в коде библиотеки, где разработчики могут свободно использовать специфичные для компилятора подсказки о «ожидаемом результате ветвления» (подумайте __builtin_expect в gcc /__assume в VC ++).

10

Решение

Перемещено из объектов в std должны быть отброшены или назначены до повторного использования. Что-нибудь это не полностью бесплатно, кроме того, что не обещано.

Иногда вещи бесплатны. Как контейнер, созданный при перемещении, пуст. Обратите внимание, что в некоторых случаях, связанных с перемещением, такой гарантии нет, поскольку некоторые реализации могут предпочесть перемещать элементы вместо буферов. Почему разница? Один был бесплатной дополнительной гарантией, другой нет.

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

Более того, a = std::move(a); является доказательством логической ошибки. Назначение: от astd) означает, что вы будете только назначать или отбрасывать a, И все же здесь вы хотите, чтобы на следующей строке было определенное состояние. Либо вы знаете, что вы назначаете себя, либо нет. Если вы этого не сделаете, то теперь вы двигаетесь от объекта, который вы также населяете, и вы этого не знаете.

Принцип «делай мелочи, чтобы обеспечить безопасность» вступает в противоречие с «ты не платишь за то, что не используешь». В этом случае победил второй.

5

Другие решения

Якк дает очень хороший ответ (как обычно и голосовал), но на этот раз я хотел добавить немного больше информации.

Политика в отношении самостоятельных заданий изменилась за последние полвека. Мы только недавно выяснили этот угловой случай в LWG 2468. На самом деле, я должен быть более точным: неформальная группа между заседаниями согласилась с решением этого вопроса, и, скорее всего, она будет проголосована за рабочий проект C ++ 1z в следующем месяце (ноябрь 2016 г.).

Суть вопроса заключается в том, чтобы изменить MoveAssignable требования прояснить, что если целью и источником назначения перемещения является один и тот же объект, то нет никаких требований к значению объекта после назначения (за исключением того, что оно должно быть действительным состоянием). Далее поясняется, что если этот объект используется с std :: lib, он все равно должен соответствовать требованиям алгоритма (например, LessThanComparable) так или иначе это было назначено движение или даже само назначение движения.

Так…

T x, y;
x = std::move(y);  // The value of y is unspecified and x == the old y
x = std::move(x);  // The value of x is unspecified

Но оба x а также y все еще в действительных состояниях. Память не просочилась. Неопределенного поведения не произошло.

Обоснование этой позиции

Это все еще производительность. Однако признается, что swap(x, x) был законным с C ++ 98 и происходит в дикой природе. Кроме того, начиная с C ++ 11 swap(x, x) выполняет самостоятельное задание x:

T temp = std::move(x);
x = std::move(x);
x = std::move(temp);

До C ++ 11, swap(x, x) был (довольно дорогой) неоперативный (с использованием копирования вместо перемещения). LWG 2468 разъясняет, что с C ++ 11 и после, swap(x, x) по-прежнему (не так дорого) не работает (с использованием перемещения вместо копирования).

Подробности:

T temp = std::move(x);
// temp now has the value of the original x, and x's value is unspecified
x = std::move(x);
// x's value is still unspecified
x = std::move(temp);
// x's value now has the value of temp, which is also the original x value

Чтобы выполнить этот запрет, самостоятельное задание на x может делать все, что захочет, пока он уходит x в действительном состоянии, не утверждая и не выбрасывая исключение.

Если вы хотите указать, что для вашего типа T само-перемещение-назначение — это запрет, это прекрасно. Std :: lib делает именно это для unique_ptr,

Если вы хотите указать, что для вашего типа U self-move-assignment оставляет его в допустимом, но неопределенном состоянии, что тоже хорошо. Std :: lib делает именно это для vector, Некоторые реализации (я полагаю, VS) идут на труд, чтобы сделать самостоятельное перемещение-назначение на vector нет оп. Другие этого не делают (например, libc ++).

3

По вопросам рекламы [email protected]