Конечно, хотелось бы видеть теоретическое обоснование, а не толкование стандарта. А так ... можно и поспорить))).
Хотя, где я, и где Herb
Не будем не рассматривать возможные специализации функции move, возьмем только стандартную реализацию.
И пройдемся по некоторым положениям свежака).
If any non-const function on an object (including moving from it) makes the object invalid, the function has a bug.
Действительно, простое применение функции move никогда не делают никакой объект инвалидом). Можно хоть миллион раз вызвать move.
Работают здесь по настоящему либо конструктор, либо оператор перемещения (такие специальные для работы с rvalue значениями).
... C++ already uses move automatically when copying from an object it knows will never be used again, such as a temporary object or a local variable being returned or thrown from a function.
Как видно, критерием автоматического применения move является знание, что экземпляр объекта не используется снова. Как правило, это временные объекты, которые разрушаются сразу после перемещения. Использование move является принудительным преобразованием к виду rvalue, подобного временному объекту, который после операции перемещения применять в дальнейшем не предполагается.
Если разделить экземпляр объекта на две составляющие - значение и токен (то, посредством чего можно обратиться к значению), то можно заметить, что rvalue - это значение без токена, а lvalue с токеном.
Логически операция move подразумевает перемещение значения из одного места в другое, и если токен (а не значение) после извлечения из него значения станет инвалидом в чем здесь проблема? Если от донора к акцептору пересадить сердце, останется ли жив донор?
Herb рассматривает пример с IndirectInt
C++ (Qt)
// Buggy class: Move leaves behind a null smart pointer
class IndirectInt {
shared_ptr<int> sp = make_shared<int>(42);
public:
// ... more functions, but using defaulted move functions
bool operator<(const IndirectInt& rhs) const { return *sp < *rhs.sp; }
// oops: unconditional deref
// ...
};
IndirectInt i[2];
i[0] = move(i[1]); // move leaves i[1].sp == nullptr
sort(begin(i), end(i)); // undefined behavior
Здесь поменяны местами причина и следствие. Утверждение, что класс некорректен следует из утверждения, что некорректным является рассмотренное поведение. Интересно, а что будет, если рассмотреть такой вариант?
double i[2];
i[1] = std::nan("0");
sort(begin(i), end(i)); // undefined behavior
Еще одно утверждение
... users won’t always know if a given object they encounter is moved-from. For example:
C++ (Qt)
void f(const IndirectInt& a, const IndirectInt& b) {
if (a < b) // this would be a bug without first testing (somehow) that a and b both aren't moved-from
// ...
}
не является правдой! Пользователь функции, вызывающий f( a, b ), всегда знает перемещались ли a или b до момента вызова. Разработчик же функции не обязан контролировать существование значений у неопциональных параметров.
Ну и корень всего)
Does the “moved-from” state correspond to the “partially formed but not well formed” described in Elements of Programming(aka EoP)?
Not quite.
In EoP, the description of an object’s state as “partially formed but not well formed” is similar to the C++ Standard’s description of “valid but unspecified.” The difference is that EoP requires such objects to be assignable and destroyable (i.e., partially formed) while the C++ standard makes a broader statement that “operations on the object behave as specified for its type” and that a moved-from object “must still meet the requirements of the library component that is using it.” (See Cpp17MoveConstructible and Cpp17MoveAssignable.)
Собственно, из такой формулировки стандарта делаются все выводы о некорректности той или иной реализации выше. При этом такая логическая модель перемещения (первичная по отношению к языку программирования) является противоречивой, в отличие от модели EoP. При этом пример not_null именно и показывает на практике противоречивость модели. Следовательно требуется корректировка стандарта. При этом многие пользователи языка и разработчики статических анализаторов сделали вывод о том, что лучшей практикой является считать, что перемещенный экземпляр объекта лучше не трогать).
А так это похоже на известный анекдот - "Ежики плакали и кололись, но продолжали жрать кактус".