В инете валяются примеры swap написанный через std::move:
template<typename T>
void swap(T& x, T& y)
{
T z = std::move(x);
x = std::move(y);
y = std::move(z);
}
Мой вопрос в том, что, к примеру в 1й строке, состояние объекта 'x' вытаскивается и ложится в 'z', оставляя 'x' в некоем состоянии с которым можно только вызвать деструктор, никак более переиспользовать нельзя.
Тут же как видно, объекту можно присвоить и потом объект можно будет использовать по полной, как обычно.
Я вижу в этом противоречие. Кто то на SO пишет, что оказвыается для STL объектов есть дополнительное требование, можно им ещё присваивать после того как с них двинули content. Но это типа только для STL. User defined типы впринципе необязаны оставаться присваемыми. Т.е. этот своп это чистый undefined behaviour.
---
Кроме того, у нас в коде я вижу кучу
some_func( std::move( my_unique_ptr ) ); // отдали владение
...
my_unique_ptr.reset( ... );
С одной стороны отдавать указатель по std::move — есть самое верное для unique_ptr. Но при этом дальнейшее переиспользование этого же указателя является прямым нарушением стандарта.
В общем связка непоняток.
---
Как правильно пользоваться объектом из которого двинули content std::move-ом?
Здравствуйте, johny5, Вы писали:
J>Мой вопрос в том, что, к примеру в 1й строке, состояние объекта 'x' вытаскивается и ложится в 'z', оставляя 'x' в некоем состоянии с которым можно только вызвать деструктор, никак более переиспользовать нельзя.
неправильный посыл в логических рассуждениях. вы рассматриваете вариант, когда после перемещения объект не должен переиспользоваться. есть много случаев, когда объект можно переиспользовать. для последних и придумали такой swap
J>Тут же как видно, объекту можно присвоить и потом объект можно будет использовать по полной, как обычно.
J>Я вижу в этом противоречие.
нет противоречий, вы их выдумали
J>User defined типы впринципе необязаны оставаться присваемыми. Т.е. этот своп это чистый undefined behaviour.
да, использование такого swap для таких типов ведет к UB. для других типов такой swap не ведет к UB и работает, как задумано
J>С одной стороны отдавать указатель по std::move — есть самое верное для unique_ptr. Но при этом дальнейшее переиспользование этого же указателя является прямым нарушением стандарта.
для unique_ptr все легально, после move его можно использовать заново (хотя формулировка стандарта все же не так внятна на этот счет)
20.8 Smart pointers [smartptr]
20.8.1 Class template unique_ptr [unique.ptr]
...
4 Additionally, u can, upon request, transfer ownership to another unique pointer u2. Upon completion of
such a transfer, the following postconditions hold:
(4.1) — u2.p is equal to the pre-transfer u.p,
(4.2) — u.p is equal to nullptr, and
(4.3) — if the pre-transfer u.d maintained state, such state has been transferred to u2.d.
As in the case of a reset, u2 must properly dispose of its pre-transfer owned object via the pre-transfer
associated deleter before the ownership transfer is considered complete. [ Note: A deleter’s state need never
be copied, only moved or swapped as ownership is transferred. —end note ]
Requires: If D is not a reference type, D shall satisfy the requirements of MoveAssignable (Table 22)
and assignment of the deleter from an rvalue of type D shall not throw an exception. Otherwise,
D is a reference type; remove_reference_t<D> shall satisfy the CopyAssignable requirements and
assignment of the deleter from an lvalue of type D shall not throw an exception.
Effects: Transfers ownership from u to *this as if by calling reset(u.release()) followed by get_deleter() = std::forward<D>(u.get_deleter()).
Returns: *this.
Здравствуйте, johny5, Вы писали:
J> оставляя 'x' в некоем состоянии с которым можно только вызвать деструктор, никак более переиспользовать нельзя.
Нет.
J>Тут же как видно, объекту можно присвоить и потом объект можно будет использовать по полной, как обычно.
Можно.
J>С одной стороны отдавать указатель по std::move — есть самое верное для unique_ptr. Но при этом дальнейшее переиспользование этого же указателя является прямым нарушением стандарта.
Нет такого в стандарте. Наоборот прямым текстом сказано ровно обратное (см. [moveconstructible] и [moveassignable]).
J> User defined типы впринципе необязаны оставаться присваемыми. Т.е. этот своп это чистый undefined behaviour.
Из-за чего это возникает "undefined behaviour"? Даже если пользовательский тип залипает в некотором состоянии после перемещения из него, это само по себе никак не приводит к неопределённому поведению. Поведение оказывается вполне определённым. Не очень удобным, но определённым.
Ну и в С++03 ты с тем же успехом мог бы определить, например, конструктор копирования, который ничего не копирует, а потом говорить, что swap для таких типов не работает (и без всяких новомодных move). Ну и что? Поведение хоть и кривое, но определённое. Да и виноват в нём всё же будет скорее не стандарт, а всё же автор тела такого конструктора.
Здравствуйте, uzhas, Вы писали:
U> есть много случаев, когда объект можно переиспользовать. для последних и придумали такой swap
Вот, мне вот эта ситуация не удовлетворяет. Какие гарантии должна давать операция move? Вот наподобии copyable: оригинальный объект остаётся логически неизменным. Что по поводу moved out? Что стандарт говорит, что можно делать с arbitrary type T после того как из него двинули? Или это зависит от типа к типу и в каждом конкретном случае нужно читать документацию? Если это так, то я не понимаю как можно подобную обобщённую операцию swap выпускать в свет, если она для каких то типов Т (standard complaint types) приведёт к undefined behaviour.
Здравствуйте, uzhas, Вы писали:
J>>User defined типы впринципе необязаны оставаться присваемыми. Т.е. этот своп это чистый undefined behaviour. U>да, использование такого swap для таких типов ведет к UB. для других типов такой swap не ведет к UB и работает, как задумано
Нет, не ведёт к UB. Не надо повторять неподтверждённые глупости. Ну или приведи пункт стандарта, тогда я признаю, что был неправ :)
Единственное место где тут может возникнуть UB — это если сам конструктор внутри себя содержит уже генератор UB (например, разыменовывает нулевой указатель). Но это ортогонально проблеме swap, так как и без него проявится уже в одиночной операции перемещения.
Здравствуйте, watchmaker, Вы писали:
W>Здравствуйте, uzhas, Вы писали:
J>>>User defined типы впринципе необязаны оставаться присваемыми. Т.е. этот своп это чистый undefined behaviour. U>>да, использование такого swap для таких типов ведет к UB. для других типов такой swap не ведет к UB и работает, как задумано W>Нет, не ведёт к UB. Не надо повторять неподтверждённые глупости. Ну или приведи пункт стандарта, тогда я признаю, что был неправ
W>Единственное место где тут может возникнуть UB — это если сам конструктор внутри себя содержит уже генератор UB (например, разыменовывает нулевой указатель). Но это ортогонально проблеме swap, так как и без него проявится уже в одиночной операции перемещения.
Я впринципе уже нашёл ответ на свой вопрос, но тут я имел ввиду что стандарт только просит чтобы user defined moved-from объект оставался в unspecified состоянии на которое можно только вызывать деструктор. Я легко могу придумать сейчас класс, moved-from standard fully conforming состояние которого будет позволять только вызов деструктора, а попытка ему присвоить выкинет этакое. Соответственно вышестоящая имплементация std::swap будет приводить к а-та-та для подобного типа Т.
Здравствуйте, watchmaker, Вы писали:
W>Нет, не ведёт к UB.
возможно, UB употреблено не к месту с формальной точки зрения.
вот рассмотрим пример: http://ideone.com/4YYAmC
как можно описать результат работы swap? какие формальные правила там нарушены?
W>Единственное место где тут может возникнуть UB
добавлю, UB может возникнуть при нарушении pre-conditions, таких как требование к типу быть MoveConstructible
в данном случае, кстати, неясно какие pre-conditions (и требования на типы) у функции swap, представленной в первом посте
Здравствуйте, uzhas, Вы писали:
U>вот рассмотрим пример: http://ideone.com/4YYAmC
Странный, однако, пример. Почему оператор перемещающего присваивания ничего не делает, если
Здравствуйте, uzhas, Вы писали:
U>Здравствуйте, watchmaker, Вы писали:
W>>Нет, не ведёт к UB. U>возможно, UB употреблено не к месту с формальной точки зрения. U>вот рассмотрим пример: http://ideone.com/4YYAmC U>как можно описать результат работы swap?
Garbage in — garbage out :)
Чаще это про данные говорят, но про поведение такого кода хочется также сказать.
Ещё можно сказать, что так писать не надл, что стоит следовать «принципу наименьшего удивления», и что либо это ошибка, либо нужно объяснить зачем так сделано (и даже в этом случае лучше так не делать). Что тут кроме этого описывать?
U>какие формальные правила там нарушены?
Вот мне кажется, что никакие. Нарушено правило, что перемещение должно создавать в некотором смысле (необязательно во всех) объект эквивалентный исходному до перемещения. Но это правило неформальное. А формальным оно становится лишь в некоторых контекстах.
Аналогично есть неформальное правило, что копирование должно создавать в некотором смысле эквивалентный объект. Но опять же правило необязательное и неуниверсальное. Даже в стандарте языка есть пример его нарушения — std::auto_ptr (к счастью уже deprecated). И ситуация с копированием auto_ptr тут ну очень похожа на исходную ситуацию с перемещением — нарушено неформальное правило, пользоваться такими классами неудобно, но можно и разрешено, если делать это аккуратно.
W>>Единственное место где тут может возникнуть UB U>добавлю, UB может возникнуть при нарушении pre-conditions, таких как требование к типу быть MoveConstructible
Во. Это отличный аргумент!
Плохо нарушать pre-conditions. Тут можно вспомнить множество примеров, скажем, когда передача компаратора в банальный std::sort приводит к выходу за границы массива из-за того, что компаратор не вводит strict weak ordering — вполне себе демонстрация UB на практике.
Вот только в твоём примере у MySwap нет среди pre-conditions требования MoveConstructible на типы аргументов. Ну по крайней мере об этом нигде не написано. Так что с формальной точки зрения поведение функции MySwap не является неопределённым. В худшем случае тут unspecified поведение из-за того, что неясно какое значение примет m_Data.empty() после перемещения из него (про это указано в описании basic_string).
Вот и получается, что поведение MySwap<A> никак не приводит к UB. Что впрочем не отменяет его бесполезности.
U>в данном случае, кстати, неясно какие pre-conditions (и требования на типы) у функции swap, представленной в первом посте
Да. Но чтобы тут было UB нужно просто явно постулировать что-то вроде «в нашей программе мы будет также называть UB следующие случаи: …» и перечислить их. То есть это не универсальное правило.