Я читаю книгу Николая М. Йосуттиса C++ Move Semantics - The Complete Guide (которая довольно хороша, имхо), и я не уверен, что согласен с комментариями в одном из примеров.
Цитата (из 6.1.2 - Гарантированные состояния перемещенных объектов):
Подобный код может быть полезен для освобождения памяти для объекта, который использует уникальный указатель:
draw(std::move(up)); // the unique pointer might or might not give up ownership
up.reset(); // ensure we give up ownership and release any resource
Предположим, что переменная up
действительно является unique_ptr
и функция draw
получает unique_ptr
по значению (иначе какой смысл перемещать указатель на функцию «передаваемую по ссылке»).
Я понимаю, что вызов reset
на "перемещенном" объекте является законным.
Но чего я не понимаю, так это почему это «требуется», чтобы «гарантировать, что мы отказываемся от права собственности и освобождаем любой ресурс», и как возможно, что «уникальный указатель может или не может отказаться от владения»?
В конце концов, unique_ptr
нельзя скопировать, и вся идея в том, что они гарантируют только одно владение.
Итак, на самом деле, если мои два предположения верны, нет необходимости вызывать функцию reset
, чтобы убедиться, что право собственности передано.
Я что-то пропустил?
Ошибка книги, наверное
Если draw
принимает ссылку rvalue, вы не знаете наверняка, принимает ли она право собственности, или просто смотрит и игнорирует указатель.
@BoP Хорошая идея. Есть ли причина для draw
использовать ссылку rvalue только для того, чтобы «взглянуть»?
Для функции под названием draw
не совсем так. Но в общем случае функция может выдать исключение до того, как завершит свою работу. Тогда кто знает?
Если draw
использует ссылку rvalue только для того, чтобы посмотреть, это не соответствует GotW # 91. Но это всего лишь GotW#91 в качестве передовой практики. Возможно, соглашение о кодировании, используемое в каком-то коде, было (на мой взгляд) не лучшей практикой ... но все же является совершенно законным кодом (неуклюжесть в стороне).
draw
может вступить во владение условно. (Например, «Если рисовать нечего, то игнорируйте параметр, так как он нам не нужен».) reset
принудительно освобождает ресурс, если draw
решил не потреблять его.
Переместить строительство и переместить назначение уже передают право собственности. Этот сброс является избыточным
Это, по крайней мере, концептуально переместит право собственности. После перемещения исходный объект должен оставаться в неопределенном, но действительном состоянии (чтобы его можно было уничтожить). Примечание std::move
на самом деле ничего не делает напрямую. Так что это действительно внутреннее устройство рисования, что с ним делать (хотя я должен признать, что нахожу тот факт, что код нуждается в документировании, довольно удивительным, было бы более последовательно, чтобы рисование всегда брало на себя ответственность. Такой плохой пример)
@Vivick Нет, фактический ход должен происходить внутри реализации draw, и эта реализация здесь не показана. Представьте, что у draw НЕТ реализации, тогда перемещение не выполняется (сам std::move не перемещается). Но да, ИМО, такая реализация нарушает правило «без сюрпризов».
Прежде всего, если параметр принимается по значению, то у нас есть гарантия, что он действительно перемещается, а не просто возможно, и что право собственности на ресурсы передается (если только конструктор перемещения ничего не делает, но это было бы бессмысленно). . В противном случае нам, возможно, придется вызывать .reset()
по двум возможным причинам:
.reset()
, если аргумент не всегда перемещается изРассмотрим следующую подпись:
void draw(std::unique_ptr<T> &&uptr);
Такая подпись, как правило, предпочтительнее, чем прием параметров перемещения по значению согласно CppCoreGuidelines F.18: Для параметров «будет перемещено из» передайте X&& и std::move параметр, хотя для такие как std::unique_ptr
.
В таком случае мы точно не знаем, перемещается ли draw
из uptr
, если не посмотрим на его реализацию:
void draw(std::unique_ptr<T> &&uptr) {
// If 'condition' is false, then 'uptr' won't be moved from, and the
// caller may have to .reset() to free up resources immediately.
if (condition) {
process_further(std::move(uptr));
}
}
.reset()
для замены назначения движенияКроме того, некоторые люди склонны реализовывать оператор присваивания перемещения следующим образом:
T& operator=(T&& other) noexcept {
this->swap(other);
return *this;
}
void draw(std::unique_ptr<T> &&uptr) {
// This effectively swaps 'something' with 'uptr', which means that
// the caller has to .reset() to free the resources in 'something',
// unless they can rely on the destructor of 'uptr' to do that.
something = std::move(uptr);
}
Это вынуждает вызывающую сторону использовать .reset()
, если они хотят, чтобы эти ресурсы были освобождены немедленно, а не тогда, когда аргумент выходит за рамки.
Никакие стандартные типы библиотек не реализуют такое назначение перемещения, и в целом это не очень хорошая идея, но она допустима и может заставить нас сделать вызов.
Нет, нам не нужно заботиться об этом.