Подтвердить что ты не робот

Избегайте самостоятельного присваивания в 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=(&&).

4b9b3361

Ответ 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 ]