Избегайте самостоятельного присваивания в std:: shuffle
Я наткнулся на следующую проблему при использовании проверенной реализации glibcxx:
/usr/include/c++/4.8.2/debug/vector:159:error: attempt to self move assign.
Objects involved in the operation:
sequence "this" @ 0x0x1b3f088 {
type = NSt7__debug6vectorIiSaIiEEE;
}
Который я привел к этому минимальному примеру:
#include <vector>
#include <random>
#include <algorithm>
struct Type {
std::vector<int> ints;
};
int main() {
std::vector<Type> intVectors = {{{1}}, {{1, 2}}};
std::shuffle(intVectors.begin(), intVectors.end(), std::mt19937());
}
Отслеживая проблему, я обнаружил, что shuffle
хочет std::swap
элемент с собой. Поскольку Type
определяется пользователем и для него не задана специализация для std::swap
, используется значение по умолчанию, которое создает временное значение и использует operator=(&&)
для переноса значений:
_Tp __tmp = _GLIBCXX_MOVE(__a);
__a = _GLIBCXX_MOVE(__b);
__b = _GLIBCXX_MOVE(__tmp);
Поскольку Type
явно не дает operator=(&&)
, он по умолчанию реализуется "рекурсивно", применяя ту же операцию к своим членам.
Проблема возникает в строке 2 кода подкачки, где __a
и __b
указывают на тот же объект, который действует в коде __a.operator=(std::move(__a))
, который затем запускает ошибку в проверенной реализации vector::operator=(&&)
.
Мой вопрос: кто это виноват?
- Это мое, потому что я должен предоставить реализацию для
swap
, которая делает "self swap" a NOP
?
- Это
std::shuffle
, потому что он не должен пытаться поменять элемент с собой?
- Является ли это проверенной версией, потому что самовозвращение - отлично?
- Все правильно, проверенная реализация просто делает мне одолжение при выполнении этой дополнительной проверки (но тогда как отключить ее)?
Я читал о перетасовке, требующей, чтобы итераторы были ValueSwappable. Разве это распространяется на самостоятельную замену (что является простой проблемой времени выполнения и не может быть реализовано с помощью проверок концепции компиляции)?
Добавление
Чтобы вызвать ошибку более непосредственно, можно использовать:
#include <vector>
int main() {
std::vector<int> vectorOfInts;
vectorOfInts = std::move(vectorOfInts);
}
Конечно, это совершенно очевидно (почему вы переместили вектор в себя?).
Если вы заменили std::vector
, то ошибка не возникла бы из-за того, что векторный класс имеет собственную реализацию функции свопинга, которая не использует operator=(&&)
.
Ответы
Ответ 1
Это ошибка в проверке GCC. Согласно стандарт С++ 11, требования к замене включают (выделение мое):
17.6.3.2 §4 Значение rvalue или lvalue t
заменяется, если и только если t
заменяется с любым значением rvalue или lvalue соответственно типа t
Любое значение rvalue или lvalue включает, по определению, t
, поэтому для замены swap(t,t)
должно быть законным. В то же время для реализации по умолчанию swap
требуется следующее
20.2.2 §2 Требуется: Тип t
должен быть MoveConstructible (Таблица 20) и MoveAssignable (Таблица 22).
Поэтому, чтобы быть заменяемым под определением стандартного оператора обмена, самоперемещение должно быть действительным и иметь постусловие, которое после того, как само присваивание t
эквивалентно ему старое значение (не обязательно no-op, хотя!) как показано в таблице 22.
Хотя объект, который вы меняете, не является стандартным типом, MoveAssignable не имеет предварительного условия, чтобы rv
и t
ссылались на разные объекты, и пока все члены MoveAssignable (как std::vector
должно быть), сгенерируйте оператор переадресации должен быть правильным (поскольку он выполняет перемещение по порядку в соответствии с пунктом 12.8 § 29). Кроме того, хотя в примечании указано, что rv
имеет действительное, но неуказанное состояние, любое состояние, за исключением того, что оно эквивалентно исходному значению, было бы неправильным для самоопределения, поскольку в противном случае постусловие было бы нарушено.
Ответ 2
Я прочитал несколько руководств о конструкторах копий и назначениях и т.д. (например this). Они все говорят, что объект должен проверять самоопределение и ничего не делать в этом случае. Таким образом, я бы сказал, что это проверенная ошибка реализации, потому что самовозвращение - отлично.
Ответ 3
Ваша реализация чрезмерно чувствительна.
Как вы хотели, стандартная цитата, подтверждающая эту точку, вот она:
17.6.3.1 Требования к аргументам шаблона
Table 22 — MoveAssignable requirements [moveassignable]
Expression Return type Return value Post-condition
t = rv T& t t is equivalent to the value of rv before the assignment
[ Note: rv remains a valid object. Its state is unspecified.—end note ]