Re: std::move, advanced
От: uzhas Ниоткуда  
Дата: 23.11.15 07:26
Оценка: 4 (1)
Здравствуйте, 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 ]

20.8.1.2.3 unique_ptr assignment [unique.ptr.single.asgn]
unique_ptr& operator=(unique_ptr&& u) noexcept;

  1. 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.
  2. Effects: Transfers ownership from u to *this as if by calling reset(u.release()) followed by get_deleter() = std::forward<D>(u.get_deleter()).
  3. Returns: *this.

Отредактировано 23.11.2015 7:35 uzhas . Предыдущая версия . Еще …
Отредактировано 23.11.2015 7:32 uzhas . Предыдущая версия .
std::move, advanced
От: johny5 Новая Зеландия
Дата: 23.11.15 02:28
Оценка: -1
Привет.

В инете валяются примеры 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-ом?
Re: std::move, advanced
От: watchmaker  
Дата: 23.11.15 07:30
Оценка:
Здравствуйте, johny5, Вы писали:

J> оставляя 'x' в некоем состоянии с которым можно только вызвать деструктор, никак более переиспользовать нельзя.

Нет.

J>Тут же как видно, объекту можно присвоить и потом объект можно будет использовать по полной, как обычно.

Можно.

J>С одной стороны отдавать указатель по std::move — есть самое верное для unique_ptr. Но при этом дальнейшее переиспользование этого же указателя является прямым нарушением стандарта.

Нет такого в стандарте. Наоборот прямым текстом сказано ровно обратное (см. [moveconstructible] и [moveassignable]).

J> User defined типы впринципе необязаны оставаться присваемыми. Т.е. этот своп это чистый undefined behaviour.

Из-за чего это возникает "undefined behaviour"? Даже если пользовательский тип залипает в некотором состоянии после перемещения из него, это само по себе никак не приводит к неопределённому поведению. Поведение оказывается вполне определённым. Не очень удобным, но определённым.
Ну и в С++03 ты с тем же успехом мог бы определить, например, конструктор копирования, который ничего не копирует, а потом говорить, что swap для таких типов не работает (и без всяких новомодных move). Ну и что? Поведение хоть и кривое, но определённое. Да и виноват в нём всё же будет скорее не стандарт, а всё же автор тела такого конструктора.
Re[2]: std::move, advanced
От: johny5 Новая Зеландия
Дата: 23.11.15 08:51
Оценка:
Здравствуйте, uzhas, Вы писали:

U> есть много случаев, когда объект можно переиспользовать. для последних и придумали такой swap


Вот, мне вот эта ситуация не удовлетворяет. Какие гарантии должна давать операция move? Вот наподобии copyable: оригинальный объект остаётся логически неизменным. Что по поводу moved out? Что стандарт говорит, что можно делать с arbitrary type T после того как из него двинули? Или это зависит от типа к типу и в каждом конкретном случае нужно читать документацию? Если это так, то я не понимаю как можно подобную обобщённую операцию swap выпускать в свет, если она для каких то типов Т (standard complaint types) приведёт к undefined behaviour.
Re[3]: std::move, advanced
От: uzhas Ниоткуда  
Дата: 23.11.15 09:00
Оценка:
Здравствуйте, johny5, Вы писали:

J>Вот, мне вот эта ситуация не удовлетворяет.

я тоже не в восторге

J> Что по поводу moved out?

ищем слово unspecified тут: http://en.cppreference.com/w/cpp/concept/MoveAssignable
недавно обсуждали подобный вопрос: http://rsdn.ru/forum/cpp/6250612.flat
Автор: Кодт
Дата: 19.11.15
Re[2]: std::move, advanced
От: watchmaker  
Дата: 23.11.15 09:12
Оценка:
Здравствуйте, uzhas, Вы писали:

J>>User defined типы впринципе необязаны оставаться присваемыми. Т.е. этот своп это чистый undefined behaviour.

U>да, использование такого swap для таких типов ведет к UB. для других типов такой swap не ведет к UB и работает, как задумано
Нет, не ведёт к UB. Не надо повторять неподтверждённые глупости. Ну или приведи пункт стандарта, тогда я признаю, что был неправ :)

Единственное место где тут может возникнуть UB — это если сам конструктор внутри себя содержит уже генератор UB (например, разыменовывает нулевой указатель). Но это ортогонально проблеме swap, так как и без него проявится уже в одиночной операции перемещения.
Re[3]: std::move, advanced
От: johny5 Новая Зеландия
Дата: 23.11.15 10:58
Оценка:
Здравствуйте, 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 будет приводить к а-та-та для подобного типа Т.
Re[3]: std::move, advanced
От: uzhas Ниоткуда  
Дата: 23.11.15 12:43
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Нет, не ведёт к UB.


возможно, UB употреблено не к месту с формальной точки зрения.
вот рассмотрим пример: http://ideone.com/4YYAmC
как можно описать результат работы swap? какие формальные правила там нарушены?

W>Единственное место где тут может возникнуть UB

добавлю, UB может возникнуть при нарушении pre-conditions, таких как требование к типу быть MoveConstructible
в данном случае, кстати, неясно какие pre-conditions (и требования на типы) у функции swap, представленной в первом посте
Re[4]: std::move, advanced
От: alexolut  
Дата: 26.11.15 09:25
Оценка:
Здравствуйте, uzhas, Вы писали:

U>вот рассмотрим пример: http://ideone.com/4YYAmC

Странный, однако, пример. Почему оператор перемещающего присваивания ничего не делает, если
m_Data.empty()
?
Re[4]: std::move, advanced
От: watchmaker  
Дата: 26.11.15 10:09
Оценка:
Здравствуйте, 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 следующие случаи: …» и перечислить их. То есть это не универсальное правило.
Отредактировано 26.11.2015 11:02 watchmaker . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.