2016-10-04 1 views
9

Стандартная политика библиотеки относительно назначения перемещения заключается в том, что the implementation is allowed to assume that self-assignment will never happen; это кажется мне очень плохой идеей, учитывая, что:В чем обоснование для операторов присваивания нестандартных операций назначения в стандартной библиотеке?

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

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


¹ Особенно в коде библиотеки, где исполнители могут свободно эксплуатировать компилятор конкретных намеков на «ветвь Ожидаемого результата» (думает, что в НКУ __builtin_expect/__assume в VC++).

+2

Найдено [этот ответ] (http://stackoverflow.com/a/9322542/4342498). Это хорошо читать, но на самом деле не отвечает на вопрос. – NathanOliver

+0

@NathanOliver: да, я уже прочитал это, и, к сожалению, это оставило меня с большим сомнением, чем раньше = ( –

+1

[LWG 2468] (http://cplusplus.github.io/LWG/lwg-active.html#2468) ' что самонаводящее присваивание приводит к действительному, но неуказанному состоянию. –

ответ

5

Предполагается, что из объектов в std отбрасываются или назначаются до их повторного использования. Ничего, который не является полностью бесплатным, кроме того, что не обещано.

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

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

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

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

+0

сторона примечание относительно: 'a = std :: move (a)' вхождения в swap(): http://stackoverflow.com/a/13129446/717732 – quetzalcoatl

+3

Отказ от исполнения совершенно абсурдный; Я бы хотел увидеть хотя бы * один * бенчмарк, где это имеет какое-либо отношение (и я могу смело ставить свои самые ценные вещи, которые никто в комитете никогда не видел такого теста). Самое главное, когда ваши проблемы с производительностью начинают быть одним дополнительным словом для предсказания филиала, вы перестаете использовать STL или какой-либо бит общего кода, который вы не контролируете полностью, напишите ваши вещи с нуля, адаптированные к вашей проблеме, делая точно * именно то, что вы хотите, и проверяйте вывод сборки критических петель при каждом изменении конфигурации. –

+0

Кроме того, я думал, что ваш второй абзац, но это не ответ на вопрос - это больше похоже на объяснение того, как эта логика реализована в стандартном. Мой вопрос в том, почему кто-то думал, что эта логика правильная. Дух движения не «разрушает хаос в перемещенном объекте и - если это не слишком много хлопот - возможно, перемещает его содержимое на объект lhs», он «перемещает содержимое в объект lhs и оставляет его однако это более удобно ». Где совпадают lhs и rhs, я не думаю, что есть сомнения в том, что я прошу (намек: это не «наносит ущерб моему объекту»). –

3

Yakk gives a very good answer (как обычно и одобрен), но на этот раз я хотел добавить немного больше информации.

В течение последних полувеков политика по самообучению значительно изменилась. Мы недавно выяснили этот угловой случай в 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 и происходит в дикой природе. Кроме того, поскольку 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) по-прежнему (не так дорого) no-op (с использованием перемещения вместо копирования).

Деталь:

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 self-move-assign - это не-op, это прекрасно. Std :: lib делает именно это для unique_ptr.

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

+0

Итак, вы можете подтвердить, что для разрешения вы продолжили «неопределенное, но действительное состояние» по соображениям производительности *? Можете ли вы показать мне те тесты, которые подсказывали вам, что тривиальное решение этой проблемы (требующее самостоятельного перемещения в стандартных контейнерах без операции) привело к измеримому и неприемлемому замедлению? –

+0

Изменение 'MoveAssignable' не делает много для самоперевода-назначения типов в стандартной библиотеке. Очень мало типов в стандартной библиотеке явно требуется «MoveAssignable». –

+0

@MatteoItalia: Нет. Слишком занято сегодня ... –

Смежные вопросы