LINUX.ORG.RU

move semantics, как обрабатывать доступ к moved-from объектам?

 ,


0

2

Проникся c++11, начал разбираться с move semantics. По ходу возник вопрос: могут быть ситуации, когда содержимое объекта перекочевало в другой посредством move, и объект остался «пустым». Всё хорошо когда «пустое» состояние объекта валидно (например, пустой vector) и/или пустой объект сразу становится недоступен (если он был временный, например). Но если оба этих условия не выполняются, как быть? Скажем, я пишу библиотеку, у меня есть какой-то класс, у объектов которого не бывает валидного пустого состояния, а пользователь библиотеки делает std::move из такого объекта него, а потом вызывает какой-то его метод. Нужно ли это предусмотреть и что в таких ситуациях делать? throw logic_error? assert? abort? ничего (UB)?

Мне пока кажется что с одной стороны, вроде самое правильное добавить assert'ы (громко падает в debug сборке и не тратит ресурсы на проверки в release), с другой - в каждый метод пихать проверку ой как не хочется.

★★★★★

а пользователь библиотеки делает std::move из такого объекта него, а потом вызывает какой-то его метод

казалось бы почему это вина библиотеки? вообще чем это отличается от delete ptr; ptr->method(); ?

x0r ★★★★★
()

могут быть ситуации, когда содержимое объекта перекочевало в другой посредством move, и объект остался «пустым»

Как оно может само «перекочевать»? Move semantics - это просто работа с rvalue references, и копировать/занулять все, что необходимо, должен сам разработчик. Просто нужно помнить, что объект, на который указывает &&ссылка, может перестать существовать (и, соответственно, вызовется его деструктор), как только эта ссылка выйдет из области видимости.

Вот, вроде бы годный туториал на эту тему, там достаточно ясно все это проиллюстрировано.

Corey
()
Последнее исправление: Corey (всего исправлений: 1)
Ответ на: комментарий от ckotinko

ты можешь запретить move приравняв соотв. конструктор и оп. присваивания к delete

Я бы никогда не догадался. Только move мне нужен.

казалось бы почему это вина библиотеки? вообще чем это отличается от delete ptr; ptr->method();

delete явный. Хотя наверное мне ещё не пришло осознание того что std::move тоже явный.

Как оно может само «перекочевать»? Move semantics - это просто работа с rvalue references, и копировать/занулять все, что необходимо, должен сам разработчик. Просто нужно помнить, что объект, на который указывает &&ссылка, может перестать существовать (и, соответственно, вызовется его деструктор), как только эта ссылка выйдет из области видимости.

Перечитайте пожалуйста на что отвечаете.

slovazap ★★★★★
() автор топика

1) Ты реализуешь move-версии конструктора и оператора присваивания, и тогда ты ДОЛЖЕН реализовать для объекта то самое «неопределенное, но валидное состояние» из документации;

2) Ты НЕ реализуешь «неопределенное, но валидное состояние» и в таком случае ДОЛЖЕН запретить move-операции для объекта.

Третьего здесь не дано. Вызов метода объекта после std::move — прямой отстрел себе ноги, потому что в документации, еще раз, ясно сказано, что перемещенный объект находится в валидном но НЕОПРЕДЕЛЕННОМ состоянии, в котором вызов любого метода объекта, кроме деструктора, приведет к НЕОПРЕДЕЛЕННОМУ поведению.

uuwaan ★★
()

Сколнен считать, что ТС что-то недопонимает. Что такое «пустой» объект? И что эта за ситуация, когда «пустота» объекта невалидна?

Если ТС приведет конкретный пример проблемной ситуации, тогда можно было бы обсудить как из этой конкретной проблемной ситуации выйти.

ИМХО семантика перемещения, это не новый обобщенный инструмент для решения широкого круга задач, это сахарок для задач одного рода: «управление жизненым циклом ресурса». Вот есть у тебя объект А и есть объект B и есть ресурс R. Пуска

pathfinder ★★★★
()
Ответ на: комментарий от uuwaan

1) Ты реализуешь move-версии конструктора и оператора присваивания, и тогда ты ДОЛЖЕН реализовать для объекта то самое «неопределенное, но валидное состояние» из документации;

Я не до конца понял что означает это «неопределенное, но валидное состояние». В примере со строкой упоминаются preconditions, а кто их определяет? То что деструктор пустого объекта не вызовет ошибок, но вызов любого метода приведёт к UB - это «неопределенное, но валидное состояние» или нет?

slovazap ★★★★★
() автор топика
Ответ на: комментарий от pathfinder

Если ТС приведет конкретный пример проблемной ситуации, тогда можно было бы обсудить как из этой конкретной проблемной ситуации выйти

Самый простой пример - moved-from unique_ptr. Т.е. который содержит nullptr. Любое разыменовывание - трындец.

slovazap ★★★★★
() автор топика
Ответ на: комментарий от slovazap

//Вот тут весь пост. Ту половинку не надо читать.

Сколнен считать, что ТС что-то недопонимает. Что такое «пустой» объект? И что эта за ситуация, когда «пустота» объекта невалидна?

Если ТС приведет конкретный пример проблемной ситуации, тогда можно было бы обсудить как из этой конкретной проблемной ситуации выйти.

ИМХО семантика перемещения, это не новый обобщенный инструмент для решения широкого круга различных задач, это сахарок для задач одного рода: «управление жизненым циклом ресурса». Вот есть у тебя объект А и есть объект B и есть ресурс R. Пускай объект А владеет ресурсом R. Жизненниый цикл ресурса R полностью определен жизненым циклом объекта A.

Мы вводим понятие «перемещение», это такая операция, когда объекты А и B обмениваются ресурсами, которыми владеют. В часном случае, когда А владеет R, а B владеет «ничем», то у нас наблюдается простая передача ресурса R от A к B. Теперь жизненый цикл ресурса R полностью определен жизненым циклом объекта B.

Все просто. Можно догадаться, что подобный функционал можно сделать и без новвоведений С++11. Просто семантика перемещения дает сахарок, который красивым образом озволяет использовать описанную выше идеологию.

pathfinder ★★★★
()
Ответ на: комментарий от slovazap

Любое разыменовывание - трындец.

Бида бида. Как дальше жить?

А ещё в этом вашем спп можно так:

int *p = 0x146;
printf("%d\n", p[100500]);

nanoolinux ★★★★
()

а пользователь библиотеки делает std::move из такого объекта него

Ну и пусть делает. Ты сам же решаешь, что делать с оставленным объектом. Можешь просто оставить его в старом состоянии. Или еще что-то такое сделать - тебе решать в конкретном случае.

anonymous
()
Ответ на: комментарий от pathfinder

Просто семантика перемещения дает сахарок

Короче, если для определенного случая бесмысленно понятие «владеет/не владеет ресурсом», то не надо использовать семантику перемещения. Либо надо заново осмыслять, какую пользу можно извлечь из подобного сахарка.

pathfinder ★★★★
()
Ответ на: комментарий от slovazap

Я не до конца понял что означает это «неопределенное, но валидное состояние».

Обычно та сторона, которая принимает rvalue reference, полученное через std::move(), сама решает, как распорядиться выковыриванием внутренностей из переданного объекта. Например, если строка реализована таким образом, что внутри нее есть char* str и size_t size, то move-конструктор может просто скопировать себе указатель и размер, а у источника - занулить их. Отсюда получится, что если после этой операции ты попробуешь сделать source[0], получишь NPE. Но может быть и такое, что ты не знаешь, что происходит внутри такого move-конструктора, поэтому тебя предупреждают: если уж передал rvalue reference, будь добр, не пользуй больше этот объект. А валидным в этом случае, я думаю, это состояние останется из-за того, что методы, не требующие соблюдения никаких инвариантов (например, string::clear()), могут продолжать функционировать.

В любом случае, если юзер сделал std::move() и потом продолжает пользоваться объектом, то он ССЗБ.

Corey
()
Последнее исправление: Corey (всего исправлений: 1)
Ответ на: комментарий от anonymous

Этот пример никак не связан с move-самантикой. nullptr у тебя по дефолту будет, например.

Ну тогда просто представь себе unique_ptr без дефолтного конструктора, без release и т.д. Т.е. который не может стать нулевым кроме как через move конструктор/присваивание.

slovazap ★★★★★
() автор топика
Ответ на: комментарий от slovazap

Как я это понимаю.

Валидное состояние — это такое состояние, при котором объект может быть корректно разрушен.

Состояние «по умолчанию» — это такое валидное, в котором находится объект после того, как отработал конструктор «по умолчанию». Важно заметить, что к этому состоянию предъявляется требование, чтобы над объектом можно было корректно производить дальнейшие операции.

А вот «валидное, но неопределенное» — это состояние «по умолчанию» МИНУС требование о возможности оперировать над объектом. В таком состоянии его можно только гарантированно корректно разрушить, и всё. Любые другие операции могут приводить к каким угодно последствиям.

uuwaan ★★
()
Ответ на: комментарий от Corey

если уж передал rvalue reference, будь добр, не пользуй больше этот объект

Но это же не правда, ибо для всех контейнеров это замечательно работает.

slovazap ★★★★★
() автор топика
Ответ на: комментарий от slovazap

для всех контейнеров это замечательно работает

Тебе никто этого не гарантирует. И если оно завтра сломается, чинить это тоже никто должен не будет.

Upd: но да, если вызывать каждый раз после move у этого контейнера clear(), думаю, что может сработать.

Corey
()
Последнее исправление: Corey (всего исправлений: 1)
Ответ на: комментарий от slovazap

Его может не быть в том смысле, что оно предъявляет требования к юзабельности объекта. Например, объект «файл» может быть нафиг никому не нужен, если он не привязан к файлу на диске, т.к. его нельзя открыть или прочитать или получить его размер. Но зато такое непривязанное состояние отлично сгодится в качестве «валидного, но неопределенного», если ты в деструкторе будешь проверять привязку перед тем, как что-то стирать с диска или закрывать дескриптор.

uuwaan ★★
()
Ответ на: комментарий от Corey

Тебе никто этого не гарантирует. И если оно завтра сломается, чинить это тоже никто должен не будет.

Пруфы будут?

Upd: но да, если вызывать каждый раз после move у этого контейнера clear(), думаю, что может сработать.

Ещё лучше. Пруфы будут?

slovazap ★★★★★
() автор топика
Ответ на: комментарий от slovazap

Пруфы будут?

http://en.cppreference.com/w/cpp/utility/move
http://en.cppreference.com/w/cpp/concept/MoveConstructible
http://en.cppreference.com/w/cpp/concept/MoveAssignable

Про valid but unspecified state уже писали выше. Поэтому пожалуйте пруфы, что после move вектором можно дальше продолжать пользоваться без принятия каких-либо дополнительных мер.

Corey
()
Ответ на: комментарий от Corey

Ok, согласен. Уточню что я осознал - move из вектора перемещает не содержимое а состояние, содержимое же остаётся неопределённым, но состояние валидным. Не уверен что в моём случае получается так.

slovazap ★★★★★
() автор топика
Последнее исправление: slovazap (всего исправлений: 3)
Ответ на: комментарий от slovazap

Ok, согласен. Уточню что я осознал - move из вектора перемещает не содержимое а состояние, содержимое же остаётся неопределённым, но состояние валидным. Не уверен что в моём случае получается так.

«Валидность» состояния означет только то, что обьект может быть корректно разрушен.

Скажем, для строки указатель на данные будет зануляться, то есть delete в деструкторе проблем не вызовет. А вот size/capacity могут остаться прежними.

DarkEld3r ★★★★★
()
Ответ на: комментарий от slovazap

move мне нужен

тогда наверно можно заприватить эти операции, создать дочерний класс и туда их вытащить usingами

ckotinko ☆☆☆
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.