Я новичок в перемещении семантики в C ++ 11, и я не очень хорошо знаю, как обрабатывать unique_ptr
параметры в конструкторах или функциях. Рассмотрим этот класс, ссылающийся на себя:
#include <memory>
class Base
{
public:
typedef unique_ptr<Base> UPtr;
Base(){}
Base(Base::UPtr n):next(std::move(n)){}
virtual ~Base(){}
void setNext(Base::UPtr n)
{
next = std::move(n);
}
protected :
Base::UPtr next;
};
Это как я должен писать функции, принимающие unique_ptr
аргументы?
И мне нужно использовать std::move
в коде вызова?
Base::UPtr b1;
Base::UPtr b2(new Base());
b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?
c++
arguments
c++11
unique-ptr
codablank1
источник
источник
Ответы:
Вот возможные способы использования уникального указателя в качестве аргумента, а также их связанное значение.
(A) По значению
Чтобы пользователь мог вызвать это, он должен выполнить одно из следующих действий:
Получение уникального указателя по значению означает, что вы передаете право владения указателем на соответствующую функцию / объект / и т. Д. После того,
newBase
как построен,nextBase
гарантированно будет пустым . Вы не являетесь владельцем объекта, и у вас даже больше нет указателя на него. Это прошло.Это гарантировано, потому что мы берем параметр по значению.
std::move
на самом деле ничего не двигает ; это просто модный актерский состав.std::move(nextBase)
возвращает ,Base&&
что является ссылкой на г-значениеnextBase
. Это все, что он делает.Поскольку
Base::Base(std::unique_ptr<Base> n)
аргумент принимает значение по значению, а не по ссылке r-value, C ++ автоматически создаст для нас временный. Это создаетstd::unique_ptr<Base>
из того,Base&&
что мы дали функцию черезstd::move(nextBase)
. Именно конструкция этого временного объекта фактически перемещает значениеnextBase
в аргумент функцииn
.(B) по неконстантной l-значной ссылке
Это должно вызываться для фактического l-значения (именованная переменная). Это не может быть вызвано с временным как это:
Смысл этого такой же, как смысл любого другого использования неконстантных ссылок: функция может требовать или не претендовать на владение указателем. Учитывая этот код:
Нет гарантии, что
nextBase
пусто. Это может быть пустым; это не может. Это действительно зависит от того, чтоBase::Base(std::unique_ptr<Base> &n)
хочет сделать. Из-за этого не очень ясно только из сигнатуры функции, что произойдет; Вы должны прочитать реализацию (или соответствующую документацию).Из-за этого я бы не предложил это как интерфейс.
(C) константным l-значением
Я не показываю реализацию, потому что вы не можете перейти от
const&
. Передаваяconst&
, вы говорите, что функция может получить доступBase
через указатель, но она не может хранить его где-либо. Он не может претендовать на владение им.Это может быть полезно. Не обязательно для вашего конкретного случая, но всегда хорошо иметь возможность вручать кому-то указатель и знать, что он не может (не нарушая правил C ++, например, не отбрасывая
const
) претендовать на владение им. Они не могут хранить это. Они могут передать это другим, но эти другие должны соблюдать те же правила.(D) по значению r
Это более или менее идентично случаю «по неконстантной ссылке на l-значение». Различия - это две вещи.
Вы можете сдать временный:
Вы должны использовать
std::move
при передаче не временных аргументов.Последнее действительно проблема. Если вы видите эту строку:
У вас есть разумное ожидание, что после завершения этой строки она
nextBase
должна быть пустой. Это должно было быть перенесено из. В конце концов, выstd::move
сидите там и говорите, что движение произошло.Проблема в том, что это не так. Это не гарантированно было перенесено из. Это может быть перемещен из, но вы будете знать только глядя на исходный код. Вы не можете сказать только из сигнатуры функции.
рекомендации
unique_ptr
, возьмите ее по значению.unique_ptr
на время выполнения этой функции, сделайте этоconst&
. В качестве альтернативы, передайте a&
илиconst&
фактическому указанному типу, а не используяunique_ptr
.&&
. Но я настоятельно рекомендую не делать этого всякий раз, когда это возможно.Как манипулировать unique_ptr
Вы не можете скопировать
unique_ptr
. Вы можете только переместить это. Правильный способ сделать это с помощьюstd::move
стандартной функции библиотеки.Если вы берете
unique_ptr
по стоимости, вы можете свободно двигаться от него. Но движение на самом деле не происходит из-заstd::move
. Примите следующее утверждение:Это действительно два утверждения:
(примечание: приведенный выше код технически не компилируется, так как невременные ссылки на r-значения на самом деле не являются r-значениями. Это здесь только для демонстрационных целей).
Это
temporary
просто ссылка на r-значениеoldPtr
. Именно в конструкторе изnewPtr
которых движение происходит.unique_ptr
Конструктор перемещения (конструктор, который принимает&&
сам к себе) - это то, что делает фактическое движение.Если у вас есть
unique_ptr
значение и вы хотите сохранить его где-то, вы должны использовать егоstd::move
для хранения.источник
std::move
не называет его возвращаемое значение. Помните, что именованные ссылки на rvalue являются lvalues. ideone.com/VlEM3unique_ptr
; может быть, некоторые другие вызывающие абоненты нуждаются в той же функциональности, но удерживаютshared_ptr
вызов вместо] (2) по ссылке lvalue. Это может быть полезно, если вызываемая функция изменяет указатель, например, добавляя или удаляя (принадлежащие списку) узлы из связанного списка.unique_ptr
значения по ссылке rvalue (например, при преобразовании их вshared_ptr
). Обоснованием этого может быть то, что он немного более эффективен (переход на временные указатели не выполняется), в то время как он дает точно такие же права вызывающей стороне (может передавать значения r или значения, заключенные в оболочкуstd::move
, но не обнаженные значения lvalue).Позвольте мне попытаться указать различные жизнеспособные способы передачи указателей на объекты, память которых управляется экземпляром
std::unique_ptr
шаблона класса; это также относится к старомуstd::auto_ptr
шаблону класса (который, я считаю, разрешает все виды использования, которые делает уникальный указатель, но для которого, кроме того, будут приниматься модифицируемые значения lvalue, где ожидаются значения rval без необходимости вызоваstd::move
), и в некоторой степени такжеstd::shared_ptr
.В качестве конкретного примера для обсуждения я рассмотрю следующий простой тип списка
Экземпляры такого списка (которым нельзя разрешить делиться частями с другими экземплярами или быть круглыми) полностью принадлежат тому, кто имеет начальный
list
указатель. Если клиентский код знает, что список, который он хранит, никогда не будет пустым, он также может предпочесть сохранять первыйnode
напрямую, а не alist
. Деструктор для определения неnode
требуется: поскольку деструкторы для его полей вызываются автоматически, умный указатель-деструктор рекурсивно удаляет весь список по окончании времени жизни начального указателя или узла.Этот рекурсивный тип дает возможность обсудить некоторые случаи, которые менее заметны в случае умного указателя на простые данные. Также сами функции иногда предоставляют (рекурсивно) пример клиентского кода. Typedef для,
list
конечно, смещен в сторонуunique_ptr
, но определение может быть изменено для использованияauto_ptr
илиshared_ptr
вместо этого без особой необходимости переходить к тому, что сказано ниже (особенно в отношении обеспечения безопасности исключений без необходимости писать деструкторы).Режимы передачи умных указателей вокруг
Режим 0: передать указатель или ссылочный аргумент вместо умного указателя
Если ваша функция не связана с владением, это предпочтительный метод: вообще не заставляйте ее использовать умный указатель. В этом случае вашей функции не нужно беспокоиться о том, кто владеет указанным объектом или каким образом осуществляется управление владением, поэтому передача необработанного указателя является как совершенно безопасной, так и наиболее гибкой формой, поскольку независимо от владельца клиент всегда может создать необработанный указатель (либо путем вызова
get
метода, либо из оператора address-of&
).Например, функция для вычисления длины такого списка должна давать не
list
аргумент, а необработанный указатель:Клиент, который содержит переменную,
list head
может вызывать эту функцию какlength(head.get())
, в то время как клиент, который выбрал вместо этого сохранениеnode n
непустого списка, может вызватьlength(&n)
.Если указатель гарантированно не равен нулю (а это не так, поскольку списки могут быть пустыми), можно предпочесть передать ссылку, а не указатель. Это может быть указатель / ссылка на не-
const
если функция должна обновлять содержимое узла (ов), без добавления или удаления любого из них (последний будет включать в себя владение).Интересным случаем, который попадает в категорию режима 0, является создание (глубокой) копии списка; хотя функция, выполняющая это, должна, конечно, передавать право собственности на копию, которую она создает, она не связана с владением списком, который она копирует. Так что это можно определить следующим образом:
Этот код заслуживает внимательного изучения, поскольку вопрос вообще объясняется, почему он компилируется (результат рекурсивного вызова
copy
в списке инициализатора связывается с ссылочным аргументом rvalue в конструкторе перемещенияunique_ptr<node>
, то естьlist
, при инициализацииnext
поля сгенерированныйnode
), а также вопрос о том, почему он безопасен для исключений (если во время процесса рекурсивного выделения памяти заканчивается и какой-то вызовnew
бросковstd::bad_alloc
, то в это время указатель на частично составленный список анонимно сохраняется во временном типеlist
создан для списка инициализатора, и его деструктор очистит этот частичный список). Кстати, следует сопротивляться искушению заменить (как я первоначально сделал) второйnullptr
поp
который, в конце концов, как известно, равен нулю в этой точке: нельзя создать умный указатель из (необработанного) указателя на константу , даже если известно, что он равен нулю.Режим 1: передать умный указатель по значению
Функция, которая принимает значение умного указателя в качестве аргумента, получает объект, на который сразу же указывается: умный указатель, который удерживал вызывающий объект (в именованной переменной или во временном анонимном случае), копируется в значение аргумента при входе в функцию, и вызывающий объект указатель стал нулевым (в случае временного копирования копия могла быть удалена, но в любом случае вызывающая сторона потеряла доступ к указанному объекту). Я хотел бы позвонить в этом режиме наличными : абонент оплачивает аванс за вызываемую услугу и не может иметь никаких иллюзий по поводу владения после звонка. Чтобы сделать это понятным, языковые правила требуют, чтобы вызывающая сторона заключила аргумент в
std::move
если смарт-указатель содержится в переменной (технически, если аргумент является lvalue); в этом случае (но не для режима 3 ниже) эта функция делает то, что предлагает ее имя, а именно, перемещает значение из переменной во временное, оставляя переменную нулевой.В случаях, когда вызываемая функция безоговорочно принимает владение указанным объектом (воровство), этот режим используется с
std::unique_ptr
илиstd::auto_ptr
является хорошим способом передачи указателя вместе с его владельцем, что позволяет избежать любого риска утечек памяти. Тем не менее, я думаю, что только в очень немногих ситуациях режим 3 не является предпочтительным (хотя бы немного) по сравнению с режимом 1. По этой причине я не буду приводить примеры использования этого режима. (Но см.reversed
Пример режима 3 ниже, где отмечается, что режим 1 будет работать как минимум так же хорошо.) Если функция принимает больше аргументов, чем только этот указатель, может случиться так, что кроме этого есть техническая причина, чтобы избежать режима 1 (сstd::unique_ptr
илиstd::auto_ptr
): так как фактическая операция перемещения происходит при передаче переменной указателяp
выражениемstd::move(p)
нельзя предполагать, что оноp
имеет полезное значение при оценке других аргументов (порядок оценки не указан), что может привести к незначительным ошибкам; в отличие от этого, использование режима 3 гарантирует, чтоp
перед вызовом функции перемещение не происходит, поэтому другие аргументы могут безопасно получить доступ к значению черезp
.При использовании с
std::shared_ptr
этим режимом интересно то, что с одним определением функции он позволяет вызывающей стороне выбирать , сохранять ли разделяемую копию указателя для себя при создании новой разделяемой копии, которая будет использоваться функцией (это происходит, когда lvalue предоставляется аргумент; конструктор копирования для общих указателей, используемых при вызове, увеличивает счетчик ссылок) или просто дает функции копию указателя, не сохраняя ее или не затрагивая счетчик ссылок (это происходит, когда предоставляется аргумент rvalue, возможно, lvalue, завернутый в зовstd::move
). НапримерТо же самое может быть достигнуто путем отдельного определения
void f(const std::shared_ptr<X>& x)
(для случая lvalue) иvoid f(std::shared_ptr<X>&& x)
(для случая rvalue), причем тела функций отличаются только тем, что первая версия вызывает семантику копирования (используя конструкцию / назначение копирования при использованииx
), но вторая версия перемещает семантику (писатьstd::move(x)
вместо этого, как в примере кода). Поэтому для общих указателей режим 1 может быть полезен, чтобы избежать некоторого дублирования кода.Режим 2: передать умный указатель с помощью (модифицируемой) ссылки на lvalue
Здесь функция просто требует наличия модифицируемой ссылки на умный указатель, но не указывает, что она будет с ней делать. Я хотел бы позвонить по этому методу по карте : звонящий обеспечивает оплату, указав номер кредитной карты. Ссылка может использоваться, чтобы стать владельцем указанного объекта, но это не обязательно. Этот режим требует предоставления модифицируемого аргумента lvalue, соответствующего тому факту, что желаемый эффект функции может включать в себя оставление полезного значения в переменной аргумента. Вызывающая сторона с выражением rvalue, которое она желает передать такой функции, будет вынуждена сохранить ее в именованной переменной, чтобы иметь возможность выполнить вызов, поскольку язык обеспечивает только неявное преобразование в константуСсылка lvalue (ссылающаяся на временную) из rvalue. ( В отличие от ситуации , противоположной от перекачиваемой
std::move
, гипсе отY&&
доY&
, сY
смарт - указательного типа, не представляется возможным, тем не менее , это преобразование может быть получен с помощью функции шаблона просто , если действительно нужной, см https://stackoverflow.com/a/24868376 / 1436796 ). В случае, когда вызываемая функция намерена безоговорочно получить право собственности на объект, украдя у аргумента, обязательство предоставить аргумент lvalue дает неправильный сигнал: переменная не будет иметь полезного значения после вызова. Поэтому режим 3, который предоставляет идентичные возможности внутри нашей функции, но просит вызывающих абонентов предоставить значение r, должен быть предпочтительным для такого использования.Однако существует действительный вариант использования для режима 2, а именно: функции, которые могут изменять указатель, или объект, на который указывает способ, который включает в себя владение . Например, функция, которая добавляет префикс к узлу,
list
предоставляет пример такого использования:Очевидно, что здесь было бы нежелательно принудительно использовать вызывающих абонентов
std::move
, поскольку их умный указатель все еще владеет четко определенным и непустым списком после вызова, хотя и другим, чем раньше.Опять же, интересно наблюдать за тем, что происходит в случае
prepend
сбоя вызова из-за недостатка свободной памяти. Тогдаnew
вызов броситstd::bad_alloc
; в этот момент времени, так как никакое неnode
может быть выделено, несомненно, что переданная ссылка rvalue (режим 3) изstd::move(l)
еще не могла быть украдена, поскольку это было бы сделано для построенияnext
поля того,node
которое не было выделено. Таким образом, оригинальный смарт-указатель по-l
прежнему содержит исходный список при возникновении ошибки; этот список будет либо должным образом уничтожен деструктором умного указателя, либо в случае,l
если он выживет благодаря достаточно раннемуcatch
предложению, он все равно будет содержать исходный список.Это был конструктивный пример; подмигнув этому вопросу, можно также привести более разрушительный пример удаления первого узла, содержащего заданное значение, если оно есть:
Опять же, правильность здесь довольно тонкая. Примечательно, что в последнем утверждении указатель,
(*p)->next
содержащийся внутри удаляемого узла, не связан (посредствомrelease
, который возвращает указатель, но возвращает исходный ноль) до того, какreset
(неявно) уничтожит этот узел (когда он уничтожит старое значение, удерживаемоеp
), гарантируя, что один и только один узел уничтожается в это время. (В альтернативной форме, упомянутой в комментарии, это время будет оставлено на усмотрение реализации оператора присваивания перемещенияstd::unique_ptr
экземпляраlist
; стандарт говорит 20.7.1.2.3; 2 что этот оператор должен действовать "как если бы звонитьreset(u.release())
", откуда и здесь время должно быть в безопасности.)Обратите внимание, что
prepend
иremove_first
не может быть вызвано клиентами, которые хранят локальнуюnode
переменную для всегда непустого списка, и это правильно, поскольку данные реализации не могут работать в таких случаях.Режим 3: передать умный указатель (изменяемая) ссылка на значение
Это предпочтительный режим для использования при владении указателем. Я хотел бы вызвать этот метод вызовом с помощью чека : вызывающая сторона должна принять отказ от владения, как если бы она предоставляла наличные, подписав чек, но фактическое снятие денег откладывается до тех пор, пока вызываемая функция не утащит указатель (точно так же, как при использовании режима 2). ). «Подписание чека» конкретно означает, что вызывающие абоненты должны заключить аргумент в
std::move
(как в режиме 1), если это lvalue (если это rvalue, часть «отказа от владения» очевидна и не требует отдельного кода).Обратите внимание, что технически режим 3 ведет себя точно так же, как режим 2, поэтому вызываемая функция не должна принимать на себя ответственность; Однако я бы настаивать на том, что если есть какая - либо неопределенность в отношении передачи прав собственности (в нормальных условиях эксплуатации), режим 2 следует предпочесть режим 3, так что режим 3 , используя неявно сигнал для вызывающих абонентов , что они будут отдающих собственность. Можно было бы возразить, что передача только аргумента режима 1 действительно сигнализирует о принудительной потере прав собственности вызывающим абонентам. Но если у клиента есть какие-либо сомнения относительно намерений вызываемой функции, он должен знать спецификации вызываемой функции, что должно устранить любые сомнения.
Удивительно сложно найти типичный пример, включающий наш
list
тип, который использует передачу аргументов режима 3. Перемещение спискаb
в конец другого спискаa
является типичным примером; однакоa
(который сохраняется и сохраняет результат операции) лучше передать с использованием режима 2:Чистым примером передачи аргумента режима 3 является следующий, который принимает список (и его владельца) и возвращает список, содержащий идентичные узлы в обратном порядке.
Эта функция может быть вызвана как in
l = reversed(std::move(l));
для обращения списка к самому себе, но обратный список также может использоваться по-другому.Здесь аргумент немедленно перемещается в локальную переменную для эффективности (можно было бы использовать параметр
l
непосредственно вместо негоp
, но тогда доступ к нему каждый раз потребовал бы дополнительного уровня косвенности); следовательно, разница с передачей аргументов в режиме 1 минимальна. Фактически, используя этот режим, аргумент мог бы служить непосредственно локальной переменной, что позволило бы избежать этого начального перемещения; это всего лишь пример общего принципа, согласно которому, если аргумент, передаваемый по ссылке, служит только для инициализации локальной переменной, можно с тем же успехом передать ее по значению и использовать параметр в качестве локальной переменной.Использование режима 3, как представляется, поддерживается стандартом, о чем свидетельствует тот факт, что все предоставляемые библиотечные функции передают владение интеллектуальными указателями с использованием режима 3. Конкретным убедительным примером является конструктор
std::shared_ptr<T>(auto_ptr<T>&& p)
. Этот конструктор использовал (instd::tr1
) для получения модифицируемой ссылки lvalue (точно так же, какauto_ptr<T>&
конструктор копирования), и поэтому мог вызываться сauto_ptr<T>
lvaluep
как instd::shared_ptr<T> q(p)
, после чегоp
был сброшен в null. В связи с переходом с режима 2 на 3 при передаче аргументов этот старый код теперь должен быть переписанstd::shared_ptr<T> q(std::move(p))
и затем будет продолжать работать. Я понимаю, что комитету здесь не понравился режим 2, но у него была возможность перейти в режим 1, определивstd::shared_ptr<T>(auto_ptr<T> p)
вместо этого они могли бы гарантировать, что старый код работает без изменений, потому что (в отличие от уникальных указателей) автоматические указатели могут быть автоматически разыменованы со значением (сам объект указателя в процессе сбрасывается до нуля). Очевидно, комитет так сильно предпочел пропагандировать режим 3, а не режим 1, что он решил активно нарушать существующий код, а не использовать режим 1 даже для уже устаревшего использования.Когда предпочитать режим 3 над режимом 1
Режим 1 идеально подходит для использования во многих случаях и может быть предпочтительным по сравнению с режимом 3 в тех случаях, когда принятие владения в противном случае принимает форму перемещения интеллектуального указателя на локальную переменную, как в
reversed
примере выше. Однако я вижу две причины предпочесть режим 3 в более общем случае:Несколько эффективнее передать ссылку, чем создать временный указатель и убрать старый указатель (обработка денег несколько трудоемка); в некоторых сценариях указатель может быть передан несколько раз без изменений в другую функцию, прежде чем он будет фактически похищен. Такое прохождение обычно требует записи
std::move
(если не используется режим 2), но обратите внимание, что это просто приведение, которое фактически ничего не делает (в частности, без разыменования), поэтому к нему прилагается нулевая стоимость.Если это возможно, что что-либо создает исключение между началом вызова функции и точкой, в которой оно (или некоторый содержащийся в нем вызов) фактически перемещает объект, на который указывает указатель, в другую структуру данных (и это исключение еще не перехвачено внутри самой функции). ), то при использовании режима 1 объект, на который ссылается умный указатель, будет уничтожен до того, как
catch
предложение сможет обработать исключение (поскольку параметр функции был уничтожен при разматывании стека), но не при использовании режима 3. Последний дает вызывающая сторона имеет возможность восстановить данные объекта в таких случаях (путем перехвата исключения). Обратите внимание, что режим 1 здесь не вызывает утечку памяти , но может привести к невосстановимой потере данных для программы, что также может быть нежелательным.Возврат умного указателя: всегда по значению
Чтобы завершить слово о возвращении умного указателя, предположительно указывающего на объект, созданный для использования вызывающей стороной. Это на самом деле не сравнимо с передачей указателей на функции, но для полноты я хотел бы настаивать на том, чтобы в таких случаях всегда возвращалось значение (и не использовалось
std::move
вreturn
выражении). Никто не хочет получить ссылку на указатель, который, вероятно, только что был отменен.источник
unique_ptr
, был ли перемещен из или нет, он все равно будет приятно удалять значение, если он все еще будет удерживать его всякий раз, когда уничтожается или используется повторно.unique_ptr
предотвращает утечку памяти (и, следовательно, в некотором смысле выполняет свой контракт), но здесь (т. Е. С использованием режима 1) это может вызвать (при определенных обстоятельствах) что-то, что можно считать еще более вредным а именно потерю данных (уничтожение указанного значения), которых можно было бы избежать с помощью режима 3.Да, если вы берете
unique_ptr
значение by в конструкторе. Простота это хорошая вещь. Поскольку он неunique_ptr
может быть скопирован (частная копия ctor), то, что вы написали, должно привести к ошибке компиляции.источник
Изменить: Этот ответ является неправильным, хотя, строго говоря, код работает. Я оставляю это здесь только потому, что обсуждение под ним слишком полезно. Этот другой ответ является лучшим ответом, данным во время моего последнего редактирования: Как передать аргумент unique_ptr в конструктор или функцию?
Основная идея
::std::move
заключается в том, что люди, которые передают вас,unique_ptr
должны использовать это, чтобы выразить знание, что они знают, чтоunique_ptr
они передают, потеряют право собственности.Это означает, что вы должны использовать
unique_ptr
в своих методах ссылку на rvalue , а не наunique_ptr
себя. Это не сработает в любом случае, потому что для передачи простого старогоunique_ptr
потребуется сделать копию, а это явно запрещено в интерфейсе дляunique_ptr
. Интересно, что использование именованной ссылки на rvalue снова превращает ее в lvalue, поэтому вам также необходимо использовать ее::std::move
в своих методах.Это означает, что ваши два метода должны выглядеть так:
Тогда люди, использующие методы, сделают это:
Как видите,
::std::move
выражение указывает на то, что указатель потеряет право собственности в точке, где его наиболее важно и полезно знать. Если бы это произошло незаметно, людям, использующим ваш класс, было бы оченьobjptr
странно внезапно потерять право собственности без очевидной причины.источник
Base fred(::std::move(objptr));
и нетBase::UPtr fred(::std::move(objptr));
?std::move
в реализации как конструктор, так и метод. И даже когда вы передаете по значению, вызывающаяstd::move
сторона все равно должна использовать для передачи lvalues. Основное отличие состоит в том, что с передачей по значению этот интерфейс дает понять, что право собственности будет потеряно. Смотрите Николь Болас комментарий к другому ответу.должно быть намного лучше, как
а также
должно быть
с тем же телом.
И ... что
evt
вhandle()
??источник
std::forward
здесь:Base::UPtr&&
это всегда Rvalue ссылочный тип, иstd::move
передает его в качестве RValue. Это уже отправлено правильно.unique_ptr
значение по значению, то вам гарантируется, что конструктор перемещения был вызван для нового значения (или просто, что вы получили временное значение). Это гарантирует, чтоunique_ptr
переменная, имеющаяся у пользователя, теперь пуста . Если вы возьмете его&&
вместо этого, он будет очищен только в том случае, если ваш код вызывает операцию перемещения. По-вашему, переменная, из которой пользователь не должен был быть перемещен, возможно. Что делает использование пользователяstd::move
подозрительным и запутанным. Использованиеstd::move
должно всегда гарантировать, что что-то было перемещено .На верх проголосовал ответ. Я предпочитаю проходить по ссылке.
Я понимаю, что может вызывать проблема передачи по ссылке. Но давайте разделим эту проблему на две стороны:
Я должен написать код
Base newBase(std::move(<lvalue>))
илиBase newBase(<rvalue>)
.Автор библиотеки должен гарантировать, что он на самом деле переместит unique_ptr для инициализации члена, если он хочет владеть владельцем.
Это все.
Если вы передадите по ссылке rvalue, она вызовет только одну инструкцию «move», но если передается по значению, это два.
Да, если автор библиотеки не является экспертом в этом, он не может переместить unique_ptr для инициализации члена, но это проблема автора, а не вас. Что бы он ни передавал по значению или по ссылке на rvalue, ваш код одинаков!
Если вы пишете библиотеку, теперь вы знаете, что должны это гарантировать, так что просто сделайте это, передавая ссылку по rvalue - лучший выбор, чем значение. Клиент, который использует вашу библиотеку, просто напишет тот же код.
Теперь по вашему вопросу. Как передать аргумент unique_ptr конструктору или функции?
Вы знаете, что является лучшим выбором.
http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html
источник