Действительно ли нам нужно гарантировать отказ от владения при перемещении `unique_ptr`?

Я читаю книгу Николая М. Йосуттиса 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, чтобы убедиться, что право собственности передано.

Я что-то пропустил?

Нет, нам не нужно заботиться об этом.

273K 16.08.2023 20:20

Ошибка книги, наверное

Michael Chourdakis 16.08.2023 20:22

Если draw принимает ссылку rvalue, вы не знаете наверняка, принимает ли она право собственности, или просто смотрит и игнорирует указатель.

BoP 16.08.2023 20:25

@BoP Хорошая идея. Есть ли причина для draw использовать ссылку rvalue только для того, чтобы «взглянуть»?

Gils 16.08.2023 20:28

Для функции под названием draw не совсем так. Но в общем случае функция может выдать исключение до того, как завершит свою работу. Тогда кто знает?

BoP 16.08.2023 20:40

Если draw использует ссылку rvalue только для того, чтобы посмотреть, это не соответствует GotW # 91. Но это всего лишь GotW#91 в качестве передовой практики. Возможно, соглашение о кодировании, используемое в каком-то коде, было (на мой взгляд) не лучшей практикой ... но все же является совершенно законным кодом (неуклюжесть в стороне).

Eljay 16.08.2023 20:47
draw может вступить во владение условно. (Например, «Если рисовать нечего, то игнорируйте параметр, так как он нам не нужен».) reset принудительно освобождает ресурс, если draw решил не потреблять его.
Raymond Chen 16.08.2023 20:53

Переместить строительство и переместить назначение уже передают право собственности. Этот сброс является избыточным

Vivick 16.08.2023 20:59

Это, по крайней мере, концептуально переместит право собственности. После перемещения исходный объект должен оставаться в неопределенном, но действительном состоянии (чтобы его можно было уничтожить). Примечание std::move на самом деле ничего не делает напрямую. Так что это действительно внутреннее устройство рисования, что с ним делать (хотя я должен признать, что нахожу тот факт, что код нуждается в документировании, довольно удивительным, было бы более последовательно, чтобы рисование всегда брало на себя ответственность. Такой плохой пример)

Pepijn Kramer 16.08.2023 21:00

@Vivick Нет, фактический ход должен происходить внутри реализации draw, и эта реализация здесь не показана. Представьте, что у draw НЕТ реализации, тогда перемещение не выполняется (сам std::move не перемещается). Но да, ИМО, такая реализация нарушает правило «без сюрпризов».

Pepijn Kramer 16.08.2023 21:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
10
70
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Прежде всего, если параметр принимается по значению, то у нас есть гарантия, что он действительно перемещается, а не просто возможно, и что право собственности на ресурсы передается (если только конструктор перемещения ничего не делает, но это было бы бессмысленно). . В противном случае нам, возможно, придется вызывать .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(), если они хотят, чтобы эти ресурсы были освобождены немедленно, а не тогда, когда аргумент выходит за рамки.

Никакие стандартные типы библиотек не реализуют такое назначение перемещения, и в целом это не очень хорошая идея, но она допустима и может заставить нас сделать вызов.

Другие вопросы по теме