С точки зрения правильного C++, при проектировании класса нужно относиться к move семантике как к деструктору, в том смысле что старый объект разрушается, правильно ли? Например такая ситуация: класс реализует долгую операцию, и содержит поля используемые в этой операции. Как быть если кто-то захочет сделать move из другого потока? Можно сделать некий lock/unlock и отслеживать перед move, но как сделать поддержку move «правильно»? Запретить move семантику для класса? Кидать исключение? Понимаю что я не совсем понимаю move :)
Здравствуйте, sanx, Вы писали:
S>С точки зрения правильного C++, при проектировании класса нужно относиться к move семантике как к деструктору, в том смысле что старый объект разрушается, правильно ли?
Нет. Посмотри, например, что происходит в стандартной библиотеке — практически все объекты просто оказываются в "valid but unspecified state". Это совсем не разрушение. Так вектор, из которого произошло перемещение, можно вполне продолжать использовать дальше (хотя, почти всегда сначала следует вызвать метод .clear, чтобы перевести его в "specified state"). И если вектор после move обладал каким-то зарезервированным участком памяти, то он так и останется им владеть — соответствующая память будет освобождена только в деструкторе.
Соответственно, нормальная практика — это ожидать, что объекты после перемещения останутся в "valid but unspecified state". И самому писать код так, чтобы это постусловие для классов выполнялось.
S>Например такая ситуация: класс реализует долгую операцию, и содержит поля используемые в этой операции. Как быть если кто-то захочет сделать move из другого потока?
А если кто-то захочет скопировать объект? А если кто-то захочет вызвать у него какой-нибудь метод? Почему именно перемещение рассматриваешь так особенно, а не более частые ситуации?
К этим проблемам move ничего не добавляет нового.
S>Запретить move семантику для класса? Кидать исключение?
Предлагаю запретить вызывать методы класса, кидать исключении при копировании. А для move можно и посложнее прикол придумать
А если серьёзно, то делай как и у других методов сделано. Если класс объявлен не thread-safe, то ничего. Если копирование происходит под мьютексом, то и перемещение пусть будет под ним же. Кидаешь исключения при обнаружении доступа из разных потоков? Ну тогда кидай их отовсюду.
Здравствуйте, sanx, Вы писали:
S>С точки зрения правильного C++, при проектировании класса нужно относиться к move семантике как к деструктору, в том смысле что старый объект разрушается, правильно ли?
«A move operation should move and leave its source in valid state... Ideally, that moved-from should be the default value of the type... The standard library assumes that it it possible to assign to a moved-from object. Always leave the moved-from object in some (necessarily specified) valid state.»
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-move-semantic
Здравствуйте, sanx, Вы писали:
S>С точки зрения правильного C++, при проектировании класса нужно относиться к move семантике как к деструктору, в том смысле что старый объект разрушается, правильно ли? Например такая ситуация: класс реализует долгую операцию, и содержит поля используемые в этой операции. Как быть если кто-то захочет сделать move из другого потока? Можно сделать некий lock/unlock и отслеживать перед move, но как сделать поддержку move «правильно»? Запретить move семантику для класса? Кидать исключение? Понимаю что я не совсем понимаю move
Проще относиться к перемещению как к частному случаю обычного присваивания. Присваивается всякая ерунда — обычно, либо пустое значение, либо то же самое, либо значение приёмника.
Нужно ли лочить объект во время присваивания? (Риторический вопрос).
Если класс сам реализует долгую операцию, то многопоточный доступ к нему надо проектировать в расчёте на задержки, блокировки, отлупы мгновенные и по таймауту.
Использовать при этом примитивный синтаксис и примитивную семантику, т.е. присваивание, копирование, перемещение, — которые подразумевают атомарное и быстрое действие, — это вводить себя в заблуждение.
Т.е. вместо
Foo f;
f.start_doing_long_async_operation();
f = Foo(123);
лучше будет что-то этакое
f.start_doing_long_async_operation();
f.try_assign_with_timeout(INFINITY, 123);
Единственное исключение из этого — когда пишется одноразовая и полностью синхронная программа. Где неограниченное ожидание в порядке вещей, а если зависнет из-за неудачно сложившихся звёзд — то и наплевать, перезапустим.