C++ 如何将 unique_ptr 参数传递给构造函数或函数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8114276/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I pass a unique_ptr argument to a constructor or a function?
提问by codablank1
I'm new to move semantics in C++11 and I don't know very well how to handle unique_ptr
parameters in constructors or functions. Consider this class referencing itself:
我是 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;
};
Is this how I should write functions taking unique_ptr
arguments?
这是我应该如何编写带unique_ptr
参数的函数吗?
And do I need to use std::move
in the calling code?
我需要std::move
在调用代码中使用吗?
Base::UPtr b1;
Base::UPtr b2(new Base());
b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?
回答by Nicol Bolas
Here are the possible ways to take a unique pointer as an argument, as well as their associated meaning.
以下是将唯一指针作为参数的可能方法,以及它们的相关含义。
(A) By Value
(A) 按价值
Base(std::unique_ptr<Base> n)
: next(std::move(n)) {}
In order for the user to call this, they must do one of the following:
为了让用户调用它,他们必须执行以下操作之一:
Base newBase(std::move(nextBase));
Base fromTemp(std::unique_ptr<Base>(new Base(...));
To take a unique pointer by value means that you are transferringownership of the pointer to the function/object/etc in question. After newBase
is constructed, nextBase
is guaranteed to be empty. You don't own the object, and you don't even have a pointer to it anymore. It's gone.
按值获取唯一指针意味着您将指针的所有权转移到相关函数/对象/等。后newBase
构造,nextBase
保证是空的。您不拥有该对象,您甚至不再拥有指向它的指针。它消失了。
This is ensured because we take the parameter by value. std::move
doesn't actually moveanything; it's just a fancy cast. std::move(nextBase)
returns a Base&&
that is an r-value reference to nextBase
. That's all it does.
这是确保的,因为我们按值获取参数。std::move
实际上并没有移动任何东西;这只是一个花哨的演员。std::move(nextBase)
返回 a Base&&
,它是对 的 r 值引用nextBase
。这就是它所做的一切。
Because Base::Base(std::unique_ptr<Base> n)
takes its argument by value rather than by r-value reference, C++ will automatically construct a temporary for us. It creates a std::unique_ptr<Base>
from the Base&&
that we gave the function via std::move(nextBase)
. It is the construction of this temporary that actually movesthe value from nextBase
into the function argument n
.
因为Base::Base(std::unique_ptr<Base> n)
通过值而不是通过 r 值引用来获取它的参数,C++ 会自动为我们构造一个临时变量。它std::unique_ptr<Base>
从Base&&
我们通过 给函数的 中创建一个std::move(nextBase)
。正是这个临时的构造实际上将值从nextBase
函数参数移入n
。
(B) By non-const l-value reference
(B) 通过非常量左值引用
Base(std::unique_ptr<Base> &n)
: next(std::move(n)) {}
This has to be called on an actual l-value (a named variable). It cannot be called with a temporary like this:
这必须在实际的左值(命名变量)上调用。它不能用这样的临时调用:
Base newBase(std::unique_ptr<Base>(new Base)); //Illegal in this case.
The meaning of this is the same as the meaning of any other use of non-const references: the function may or may notclaim ownership of the pointer. Given this code:
this 的含义与任何其他使用非常量引用的含义相同:函数可能会或可能不会声明指针的所有权。鉴于此代码:
Base newBase(nextBase);
There is no guarantee that nextBase
is empty. It maybe empty; it may not. It really depends on what Base::Base(std::unique_ptr<Base> &n)
wants to do. Because of that, it's not very evident just from the function signature what's going to happen; you have to read the implementation (or associated documentation).
不能保证nextBase
是空的。它可能是空的;可能不会。这真的取决于Base::Base(std::unique_ptr<Base> &n)
想要做什么。正因为如此,仅仅从函数签名中不太清楚会发生什么;您必须阅读实现(或相关文档)。
Because of that, I wouldn't suggest this as an interface.
因此,我不建议将其作为接口。
(C) By const l-value reference
(C) 通过 const l 值引用
Base(std::unique_ptr<Base> const &n);
I don't show an implementation, because you cannotmove from a const&
. By passing a const&
, you are saying that the function can access the Base
via the pointer, but it cannot storeit anywhere. It cannot claim ownership of it.
我没有展示一个实现,因为你不能从const&
. 通过传递 a const&
,您是说该函数可以Base
通过指针访问,但不能将其存储在任何地方。它不能声称拥有它。
This can be useful. Not necessarily for your specific case, but it's always good to be able to hand someone a pointer and know that they cannot(without breaking rules of C++, like no casting away const
) claim ownership of it. They can't store it. They can pass it to others, but those others have to abide by the same rules.
这很有用。不一定适用于您的特定情况,但能够将指针交给某人并且知道他们不能(不违反 C++ 规则,例如不丢弃const
)声明其所有权总是好的。他们不能储存它。他们可以将它传递给其他人,但其他人必须遵守相同的规则。
(D) By r-value reference
(D) 通过 r 值参考
Base(std::unique_ptr<Base> &&n)
: next(std::move(n)) {}
This is more or less identical to the "by non-const l-value reference" case. The differences are two things.
这或多或少与“by non-const l-value reference”情况相同。差异是两件事。
You canpass a temporary:
Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
You mustuse
std::move
when passing non-temporary arguments.
你可以通过一个临时的:
Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
您必须使用
std::move
通过非暂时性的参数时。
The latter is really the problem. If you see this line:
后者才是真正的问题。如果你看到这一行:
Base newBase(std::move(nextBase));
You have a reasonable expectation that, after this line completes, nextBase
should be empty. It should have been moved from. After all, you have that std::move
sitting there, telling you that movement has occurred.
你有一个合理的期望,在这行完成后,nextBase
应该是空的。它应该被移出。毕竟,你std::move
坐在那里,告诉你已经发生了运动。
The problem is that it hasn't. It is not guaranteedto have been moved from. It mayhave been moved from, but you will only know by looking at the source code. You cannot tell just from the function signature.
问题是它没有。不保证已被移出。它可能已被移出,但您只能通过查看源代码才能知道。您不能仅从函数签名中判断。
Recommendations
建议
- (A) By Value:If you mean for a function to claim ownershipof a
unique_ptr
, take it by value. - (C) By const l-value reference:If you mean for a function to simply use the
unique_ptr
for the duration of that function's execution, take it byconst&
. Alternatively, pass a&
orconst&
to the actual type pointed to, rather than using aunique_ptr
. - (D) By r-value reference:If a function may or may not claim ownership (depending on internal code paths), then take it by
&&
. But I strongly advise against doing this whenever possible.
- (A) 按值:如果您的意思是让函数声明对 a 的所有权
unique_ptr
,请按值获取。 - (C) 通过 const l-value 引用:如果你的意思是一个函数在
unique_ptr
该函数的执行期间简单地使用,那么把它通过const&
。或者,将 a&
或传递给const&
指向的实际类型,而不是使用 aunique_ptr
。 - (D) 通过 r 值引用:如果一个函数可能会或可能不会声明所有权(取决于内部代码路径),则通过
&&
. 但我强烈建议尽可能不要这样做。
How to manipulate unique_ptr
如何操作 unique_ptr
You cannot copy a unique_ptr
. You can only move it. The proper way to do this is with the std::move
standard library function.
您不能复制unique_ptr
. 你只能移动它。正确的方法是使用std::move
标准库函数。
If you take a unique_ptr
by value, you can move from it freely. But movement doesn't actually happen because of std::move
. Take the following statement:
如果你采用一个unique_ptr
by 值,你可以自由地从中移动。但运动实际上并没有发生,因为std::move
。采取以下声明:
std::unique_ptr<Base> newPtr(std::move(oldPtr));
This is really two statements:
这实际上是两个陈述:
std::unique_ptr<Base> &&temporary = std::move(oldPtr);
std::unique_ptr<Base> newPtr(temporary);
(note: The above code does not technically compile, since non-temporary r-value references are not actually r-values. It is here for demo purposes only).
(注意:上面的代码在技术上不能编译,因为非临时 r 值引用实际上不是 r 值。这里仅用于演示目的)。
The temporary
is just an r-value reference to oldPtr
. It is in the constructorof newPtr
where the movement happens. unique_ptr
's move constructor (a constructor that takes a &&
to itself) is what does the actual movement.
的temporary
仅仅是一个r值参考oldPtr
。它位于运动发生位置的构造函数中newPtr
。unique_ptr
的移动构造函数(一个将 a&&
用于自身的构造函数)是实际移动的内容。
If you have a unique_ptr
value and you want to store it somewhere, you mustuse std::move
to do the storage.
如果你有一个unique_ptr
值并且你想把它存储在某个地方,你必须使用std::move
它来做存储。
回答by Marc van Leeuwen
Let me try to state the different viable modes of passing pointers around to objects whose memory is managed by an instance of the std::unique_ptr
class template; it also applies to the the older std::auto_ptr
class template (which I believe allows all uses that unique pointer does, but for which in addition modifiable lvalues will be accepted where rvalues are expected, without having to invoke std::move
), and to some extent also to std::shared_ptr
.
让我尝试说明将指针传递给内存由std::unique_ptr
类模板实例管理的对象的不同可行模式;它也适用于较旧的std::auto_ptr
类模板(我相信它允许唯一指针所做的所有使用,但除此之外,在需要右值的地方将接受可修改的左值,而不必调用std::move
),并且在某种程度上也适用于std::shared_ptr
.
As a concrete example for the discussion I will consider the following simple list type
作为讨论的具体示例,我将考虑以下简单列表类型
struct node;
typedef std::unique_ptr<node> list;
struct node { int entry; list next; }
Instances of such list (which cannot be allowed to share parts with other instances or be circular) are entirely owned by whoever holds the initial list
pointer. If client code knows that the list it stores will never be empty, it may also choose to store the first node
directly rather than a list
.
No destructor for node
needs to be defined: since the destructors for its fields are automatically called, the whole list will be recursively deleted by the smart pointer destructor once the lifetime of initial pointer or node ends.
此类列表的实例(不允许与其他实例共享部分或循环)完全由持有初始list
指针的人所有。如果客户端代码知道它存储的列表永远不会为空,它也可以选择node
直接存储第一个而不是一个list
。不需要node
定义析构函数:由于其字段的析构函数是自动调用的,一旦初始指针或节点的生命周期结束,整个列表将被智能指针析构函数递归删除。
This recursive type gives the occasion to discuss some cases that are less visible in the case of a smart pointer to plain data. Also the functions themselves occasionally provide (recursively) an example of client code as well. The typedef for list
is of course biased towards unique_ptr
, but the definition could be changed to use auto_ptr
or shared_ptr
instead without much need to change to what is said below (notably concerning exception safety being assured without the need to write destructors).
这种递归类型让我们有机会讨论一些在智能指针指向普通数据的情况下不太明显的情况。此外,函数本身偶尔也会(递归地)提供一个客户端代码示例。for 的 typedeflist
当然偏向于unique_ptr
,但定义可以更改为 use ,auto_ptr
或者shared_ptr
不需要太多更改为下面所说的内容(特别是在不需要编写析构函数的情况下确保异常安全)。
Modes of passing smart pointers around
传递智能指针的模式
Mode 0: pass a pointer or reference argument instead of a smart pointer
模式 0:传递指针或引用参数而不是智能指针
If your function is not concerned with ownership, this is the preferred method: don't make it take a smart pointer at all. In this case your function does not need to worry whoowns the object pointed to, or by what means that ownership is managed, so passing a raw pointer is both perfectly safe, and the most flexible form, since regardless of ownership a client can always produce a raw pointer (either by calling the get
method or from the address-of operator &
).
如果您的函数不关心所有权,这是首选方法:根本不要让它采用智能指针。在这种情况下,您的函数不需要担心谁拥有指向的对象,或者所有权是通过什么方式管理的,因此传递原始指针既是完全安全的,也是最灵活的形式,因为无论所有权如何,客户端始终可以产生一个原始指针(通过调用get
方法或从操作符的地址&
)。
For instance the function to compute the length of such list, should not be give a list
argument, but a raw pointer:
例如,计算此类列表长度的函数不应给出list
参数,而应给出原始指针:
size_t length(const node* p)
{ size_t l=0; for ( ; p!=nullptr; p=p->next.get()) ++l; return l; }
A client that holds a variable list head
can call this function as length(head.get())
,
while a client that has chosen instead to store a node n
representing a non-empty list can call length(&n)
.
持有变量的客户端list head
可以调用此函数作为length(head.get())
,而选择存储node n
表示非空列表的客户端可以调用length(&n)
。
If the pointer is guaranteed to be non null (which is not the case here since lists may be empty) one might prefer to pass a reference rather than a pointer. It might be a pointer/reference to non-const
if the function needs to update the contents of the node(s), without adding or removing any of them (the latter would involve ownership).
如果指针保证非空(这里不是这种情况,因为列表可能为空),人们可能更喜欢传递引用而不是指针。const
如果函数需要更新节点的内容,而不添加或删除它们中的任何一个(后者将涉及所有权),则它可能是指向 non- 的指针/引用。
An interesting case that falls in the mode 0 category is making a (deep) copy of the list; while a function doing this must of course transfer ownership of the copy it creates, it is not concerned with the ownership of the list it is copying. So it could be defined as follows:
属于模式 0 类别的一个有趣案例是制作列表的(深层)副本;虽然执行此操作的函数当然必须转移其创建的副本的所有权,但它并不关心它正在复制的列表的所有权。所以它可以定义如下:
list copy(const node* p)
{ return list( p==nullptr ? nullptr : new node{p->entry,copy(p->next.get())} ); }
This code merits a close look, both for the question as to why it compiles at all (the result of the recursive call to copy
in the initialiser list binds to the rvalue reference argument in the move constructor of unique_ptr<node>
, a.k.a. list
, when initialising the next
field of the generated node
), and for the question as to why it is exception-safe (if during the recursive allocation process memory runs out and some call of new
throws std::bad_alloc
, then at that time a pointer to the partly constructed list is held anonymously in a temporary of type list
created for the initialiser list, and its destructor will clean up that partial list). By the way one should resist the temptation to replace (as I initially did) the second nullptr
by p
, which after all is known to be null at that point: one cannot construct a smart pointer from a (raw) pointer to constant, even when it is known to be null.
这段代码值得仔细研究一下,无论是关于它为什么编译的问题(copy
在初始化列表中递归调用的结果绑定到移动构造函数中的右值引用参数unique_ptr<node>
,也就是list
在初始化next
字段时生成node
),以及关于为什么它是异常安全的问题(如果在递归分配过程中内存用完并且有一些new
throws调用std::bad_alloc
,那么那时指向部分构造列表的指针被匿名保存在一个临时类型中list
为初始化列表创建,其析构函数将清理该部分列表)。顺便说一个人应该抵制诱惑,以取代(正如最初我)第二个nullptr
通过p
,毕竟那时已知它为空:即使已知它为空,也无法从(原始)指针构造智能指针到 constant。
Mode 1: pass a smart pointer by value
方式一:按值传递智能指针
A function that takes a smart pointer value as argument takes possession of the object pointed to right away: the smart pointer that the caller held (whether in a named variable or an anonymous temporary) is copied into the argument value at function entrance and the caller's pointer has become null (in the case of a temporary the copy might have been elided, but in any case the caller has lost access to the pointed to object). I would like to call this mode call by cash: caller pays up front for the service called, and can have no illusions about ownership after the call. To make this clear, the language rules require the caller to wrap the argument in std::move
if the smart pointer is held in a variable (technically, if the argument is an lvalue); in this case (but not for mode 3 below) this function does what its name suggests, namely move the value from the variable to a temporary, leaving the variable null.
将智能指针值作为参数的函数立即拥有指向的对象:调用者持有的智能指针(无论是在命名变量中还是匿名临时变量中)被复制到函数入口处的参数值和调用者的指针已变为空(在临时的情况下,副本可能已被省略,但在任何情况下,调用者都无法访问所指向的对象)。我想通过现金调用这种模式调用:调用者预先为调用的服务付费,并且在调用后不能对所有权抱有任何幻想。为了说明这一点,语言规则要求调用者将参数包装在std::move
如果智能指针保存在变量中(从技术上讲,如果参数是左值);在这种情况下(但不适用于下面的模式 3),此函数执行其名称所暗示的操作,即将值从变量移动到临时变量,使变量为空。
For cases where the called function unconditionally takes ownership of (pilfers) the pointed-to object, this mode used with std::unique_ptr
or std::auto_ptr
is a good way of passing a pointer together with its ownership, which avoids any risk of memory leaks. Nonetheless I think that there are only very few situations where mode 3 below is not to be preferred (ever so slightly) over mode 1. For this reason I shall provide no usage examples of this mode. (But see the reversed
example of mode 3 below, where it is remarked that mode 1 would do at least as well.) If the function takes more arguments than just this pointer, it may happen that there is in addition a technical reason to avoid mode 1(with std::unique_ptr
or std::auto_ptr
): since an actual move operation takes place while passing a pointer variable p
by the expression std::move(p)
, it cannot be assumed that p
holds a useful value while evaluating the other arguments (the order of evaluation being unspecified), which could lead to subtle errors; by contrast, using mode 3 assures that no move from p
takes place before the function call, so other arguments can safely access a value through p
.
对于被调用函数无条件获取(窃取)指向对象的所有权的情况,这种模式与std::unique_ptr
或std::auto_ptr
一起使用是将指针及其所有权一起传递的好方法,这避免了任何内存泄漏的风险。尽管如此,我认为只有极少数情况下下面的模式 3 不比模式 1 更受欢迎(稍微)。因此,我将不提供这种模式的使用示例。(但请参阅reversed
下面模式 3的示例,其中提到模式 1 至少也可以这样做。)如果函数接受更多参数而不仅仅是这个指针,则可能会发生另外一个技术原因来避免模式1(with std::unique_ptr
or std::auto_ptr
): 因为在传递指针变量时发生了实际的移动操作p
根据表达式std::move(p)
,不能假设p
在评估其他参数时拥有有用的值(未指定评估顺序),这可能会导致细微的错误;相比之下,使用模式 3 可确保p
在函数调用之前不会发生移动,因此其他参数可以安全地通过 访问值p
。
When used with std::shared_ptr
, this mode is interesting in that with a single function definition it allows the caller to choosewhether to keep a sharing copy of the pointer for itself while creating a new sharing copy to be used by the function (this happens when an lvalue argument is provided; the copy constructor for shared pointers used at the call increases the reference count), or to just give the function a copy of the pointer without retaining one or touching the reference count (this happens when a rvalue argument is provided, possibly an lvalue wrapped in a call of std::move
). For instance
使用 with 时std::shared_ptr
,这种模式很有趣,因为它允许调用者选择是否为自己保留指针的共享副本,同时创建供函数使用的新共享副本(这发生在左值提供了参数;在调用中使用的共享指针的复制构造函数增加了引用计数),或者只是给函数一个指针的副本而不保留一个或接触引用计数(这发生在提供了右值参数时,可能包含在std::move
)调用中的左值。例如
void f(std::shared_ptr<X> x) // call by shared cash
{ container.insert(std::move(x)); } // store shared pointer in container
void client()
{ std::shared_ptr<X> p = std::make_shared<X>(args);
f(p); // lvalue argument; store pointer in container but keep a copy
f(std::make_shared<X>(args)); // prvalue argument; fresh pointer is just stored away
f(std::move(p)); // xvalue argument; p is transferred to container and left null
}
The same could be achieved by separately defining void f(const std::shared_ptr<X>& x)
(for the lvalue case) and void f(std::shared_ptr<X>&& x)
(for the rvalue case), with function bodies differing only in that the first version invokes copy semantics (using copy construction/assignment when using x
) but the second version move semantics (writing std::move(x)
instead, as in the example code). So for shared pointers, mode 1 can be useful to avoid some code duplication.
同样可以通过分别定义void f(const std::shared_ptr<X>& x)
(对于左值情况)和void f(std::shared_ptr<X>&& x)
(对于右值情况)来实现,函数体的不同仅在于第一个版本调用复制语义(使用时使用复制构造/赋值x
),但第二个版本调用移动语义(std::move(x)
改为编写,如示例代码中所示)。因此对于共享指针,模式 1 可用于避免一些代码重复。
Mode 2: pass a smart pointer by (modifiable) lvalue reference
模式 2:通过(可修改的)左值引用传递智能指针
Here the function just requires having a modifiable reference to the smart pointer, but gives no indication of what it will do with it. I would like to call this method call by card: caller ensures payment by giving a credit card number. The reference canbe used to take ownership of the pointed-to object, but it does not have to. This mode requires providing a modifiable lvalue argument, corresponding to the fact that the desired effect of the function may include leaving a useful value in the argument variable. A caller with an rvalue expression that it wishes to pass to such a function would be forced to store it in a named variable to be able to make the call, since the language only provides implicit conversion to a constantlvalue reference (referring to a temporary) from an rvalue. (Unlike the opposite situation handled by std::move
, a cast from Y&&
to Y&
, with Y
the smart pointer type, is not possible; nonetheless this conversion could be obtained by a simple template function if really desired; see https://stackoverflow.com/a/24868376/1436796). For the case where the called function intends to unconditionally take ownership of the object, stealing from the argument, the obligation to provide an lvalue argument is giving the wrong signal: the variable will have no useful value after the call. Therefore mode 3, which gives identical possibilities inside our function but asks callers to provide an rvalue, should be preferred for such usage.
这里的函数只需要对智能指针有一个可修改的引用,但没有说明它将用它做什么。我想通过卡调用这种方法:来电者通过提供信用卡号码来确保付款。引用可用于获取指向对象的所有权,但并非必须如此。这种模式需要提供一个可修改的左值参数,对应于这样一个事实,即函数的预期效果可能包括在参数变量中留下一个有用的值。具有希望传递给此类函数的右值表达式的调用者将被迫将其存储在命名变量中以便能够进行调用,因为该语言仅提供对常量的隐式转换来自右值的左值引用(指临时的)。(与 处理的相反情况不同,无法使用智能指针类型std::move
从Y&&
to进行强制转换;不过,如果确实需要,可以通过简单的模板函数获得这种转换;请参阅https://stackoverflow.com/a/24868376 /1436796)。对于被调用函数打算无条件获取对象所有权的情况,从参数中窃取,提供左值参数的义务会给出错误的信号:调用后变量将没有有用的值。因此,模式 3 在我们的函数中提供了相同的可能性,但要求调用者提供右值,对于这种用法应该是首选。Y&
Y
However there is a valid use case for mode 2, namely functions that may modifythe pointer, or the object pointed to in a way that involves ownership. For instance, a function that prefixes a node to a list
provides an example of such use:
然而,模式 2 有一个有效的用例,即可以修改指针或以涉及所有权的方式指向的对象的函数。例如,将节点作为 a 前缀的函数list
提供了这样使用的示例:
void prepend (int x, list& l) { l = list( new node{ x, std::move(l)} ); }
Clearly it would be undesirable here to force callers to use std::move
, since their smart pointer still owns a well defined and non-empty list after the call, though a different one than before.
显然,在这里强制调用者使用 是不可取的std::move
,因为他们的智能指针在调用后仍然拥有一个定义明确的非空列表,尽管与之前不同。
Again it is interesting to observe what happens if the prepend
call fails for lack of free memory. Then the new
call will throw std::bad_alloc
; at this point in time, since no node
could be allocated, it is certain that the passed rvalue reference (mode 3) from std::move(l)
cannot yet have been pilfered, as that would be done to construct the next
field of the node
that failed to be allocated. So the original smart pointer l
still holds the original list when the error is thrown; that list will either be properly destroyed by the smart pointer destructor, or in case l
should survive thanks to a sufficiently early catch
clause, it will still hold the original list.
同样有趣的是观察如果prepend
调用因缺少可用内存而失败会发生什么。然后new
调用会抛出std::bad_alloc
;此时,由于node
无法分配no ,因此可以肯定的是, from 传递的右值引用(模式 3)std::move(l)
还不能被窃取,因为这将用于构造未能分配的next
字段node
。所以l
当抛出错误时,原来的智能指针仍然持有原来的列表;该列表要么被智能指针析构函数正确销毁,要么l
由于足够早的catch
子句而幸存下来,它仍将保留原始列表。
That was a constructive example; with a wink to this questionone can also give the more destructive example of removing the first node containing a given value, if any:
这是一个建设性的例子;对这个问题眨眼,还可以给出更具破坏性的示例,即删除包含给定值的第一个节点(如果有):
void remove_first(int x, list& l)
{ list* p = &l;
while ((*p).get()!=nullptr and (*p)->entry!=x)
p = &(*p)->next;
if ((*p).get()!=nullptr)
(*p).reset((*p)->next.release()); // or equivalent: *p = std::move((*p)->next);
}
Again the correctness is quite subtle here. Notably, in the final statement the pointer (*p)->next
held inside the node to be removed is unlinked (by release
, which returns the pointer but makes the original null) beforereset
(implicitly) destroys that node (when it destroys the old value held by p
), ensuring that one and only onenode is destroyed at that time. (In the alternative form mentioned in the comment, this timing would be left to the internals of the implementation of the move-assignment operator of the std::unique_ptr
instance list
; the standard says 20.7.1.2.3;2 that this operator should act "as if by calling reset(u.release())
", whence the timing should be safe here too.)
同样,这里的正确性非常微妙。值得注意的是,在最后一条语句中,在(*p)->next
要删除的节点内保存的指针是未链接的(by release
,它返回指针但使原始指针为空)在reset
(隐式)销毁该节点之前(当它销毁由 持有的旧值时p
),确保此时只有一个节点被销毁。(在评论中提到的替代形式中,这个时间将留给std::unique_ptr
实例的移动赋值运算符的实现的内部list
;标准说 20.7.1.2.3;2 这个运算符应该“好像通过调用reset(u.release())
“,因此时间在这里也应该是安全的。)
Note that prepend
and remove_first
cannot be called by clients who store a local node
variable for an always non-empty list, and rightly so since the implementations given could not work for such cases.
请注意,prepend
并remove_first
不能由谁存储在本地客户端调用node
变量始终非空列表,这是正确的,因为在给定的实现不能为这样的情况下工作。
Mode 3: pass a smart pointer by (modifiable) rvalue reference
模式 3:通过(可修改的)右值引用传递智能指针
This is the preferred mode to use when simply taking ownership of the pointer. I would like to call this method call by check: caller must accept relinquishing ownership, as if providing cash, by signing the check, but the actual withdrawal is postponed until the called function actually pilfers the pointer (exactly as it would when using mode 2). The "signing of the check" concretely means callers have to wrap an argument in std::move
(as in mode 1) if it is an lvalue (if it is an rvalue, the "giving up ownership" part is obvious and requires no separate code).
这是在简单地获得指针所有权时使用的首选模式。我想通过支票调用这个方法:调用者必须接受放弃所有权,就像提供现金一样,通过签署支票,但实际的提款被推迟,直到被调用的函数实际窃取了指针(就像使用模式 2 时那样) )。“签名检查”具体意味着调用者必须将参数包装在std::move
(如在模式 1 中)如果它是左值(如果它是右值,“放弃所有权”部分是显而易见的,不需要单独的代码)。
Note that technically mode 3 behaves exactly as mode 2, so the called function does not have toassume ownership; however I would insist that if there is any uncertainty about ownership transfer (in normal usage), mode 2 should be preferred to mode 3, so that using mode 3 is implicitly a signal to callers that they aregiving up ownership. One might retort that only mode 1 argument passing really signals forced loss of ownership to callers. But if a client has any doubts about intentions of the called function, she is supposed to know the specifications of the function being called, which should remove any doubt.
请注意,从技术上讲,模式 3 的行为与模式 2 完全相同,因此被调用的函数不必承担所有权;但是我坚持认为,如果所有权转移(在正常使用中)存在任何不确定性,则模式 2 应该优先于模式 3,因此使用模式 3 是向调用者发出他们正在放弃所有权的隐式信号。有人可能会反驳说,只有模式 1 参数传递才真正标志着调用者被迫失去所有权。但是如果客户对被调用函数的意图有任何疑问,她应该知道被调用函数的规格,这应该消除任何疑问。
It is surprisingly difficult to find a typical example involving our list
type that uses mode 3 argument passing. Moving a list b
to the end of another list a
is a typical example; however a
(which survives and holds the result of the operation) is better passed using mode 2:
很难找到一个典型的例子,涉及我们list
使用模式 3 参数传递的类型。将一个列表移动b
到另一个列表的末尾a
是一个典型的例子;但是a
(它保留并保存操作的结果)最好使用模式 2 传递:
void append (list& a, list&& b)
{ list* p=&a;
while ((*p).get()!=nullptr) // find end of list a
p=&(*p)->next;
*p = std::move(b); // attach b; the variable b relinquishes ownership here
}
A pure example of mode 3 argument passing is the following that takes a list (and its ownership), and returns a list containing the identical nodes in reverse order.
下面是模式 3 参数传递的一个纯粹示例,它接受一个列表(及其所有权),并以相反的顺序返回一个包含相同节点的列表。
list reversed (list&& l) noexcept // pilfering reversal of list
{ list p(l.release()); // move list into temporary for traversal
list result(nullptr);
while (p.get()!=nullptr)
{ // permute: result --> p->next --> p --> (cycle to result)
result.swap(p->next);
result.swap(p);
}
return result;
}
This function might be called as in l = reversed(std::move(l));
to reverse the list into itself, but the reversed list can also be used differently.
这个函数可能会被调用为 inl = reversed(std::move(l));
将列表反转为它自己,但反转列表也可以以不同的方式使用。
Here the argument is immediately moved to a local variable for efficiency (one could have used the parameter l
directly in the place of p
, but then accessing it each time would involve an extra level of indirection); hence the difference with mode 1 argument passing is minimal. In fact using that mode, the argument could have served directly as local variable, thus avoiding that initial move; this is just an instance of the general principle that if an argument passed by reference only serves to initialise a local variable, one might just as well pass it by value instead and use the parameter as local variable.
这里参数立即移动到局部变量以提高效率(可以l
直接在 的位置使用参数p
,但每次访问它都会涉及额外的间接级别);因此与模式 1 参数传递的差异很小。实际上使用这种模式,参数可以直接用作局部变量,从而避免初始移动;这只是一般原则的一个实例,如果通过引用传递的参数仅用于初始化局部变量,那么最好还是通过值传递它并将参数用作局部变量。
Using mode 3 appears to be advocated by the standard, as witnessed by the fact that all provided library functions that transfer ownership of smart pointers using mode 3. A particular convincing case in point is the constructor std::shared_ptr<T>(auto_ptr<T>&& p)
. That constructor used (in std::tr1
) to take a modifiable lvaluereference (just like the auto_ptr<T>&
copy constructor), and could therefore be called with an auto_ptr<T>
lvalue p
as in std::shared_ptr<T> q(p)
, after which p
has been reset to null. Due to the change from mode 2 to 3 in argument passing, this old code must now be rewritten to std::shared_ptr<T> q(std::move(p))
and will then continue to work. I understand that the committee did not like the mode 2 here, but they had the option of changing to mode 1, by defining std::shared_ptr<T>(auto_ptr<T> p)
instead, they could have ensured that old code works without modification, because (unlike unique-pointers) auto-pointers can be silently dereferenced to a value (the pointer object itself being reset to null in the process). Apparently the committee so much preferred advocating mode 3 over mode 1, that they chose to actively break existing coderather than to use mode 1 even for an already deprecated usage.
标准似乎提倡使用模式 3,正如所有提供的使用模式 3 转移智能指针所有权的库函数所证明的那样。一个特别有说服力的例子是构造函数std::shared_ptr<T>(auto_ptr<T>&& p)
。该构造函数使用 (in std::tr1
) 来获取可修改的左值引用(就像auto_ptr<T>&
复制构造函数一样),因此可以使用auto_ptr<T>
左值调用p
in std::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)
相反,他们可以确保旧代码无需修改即可工作,因为(与唯一指针不同)自动指针可以静默地解除对值的引用(指针对象本身在此过程中被重置为 null)。显然,委员会更喜欢提倡模式 3 而不是模式 1,以至于他们选择积极破坏现有代码而不是使用模式 1,即使是已经弃用的用法。
When to prefer mode 3 over mode 1
何时更喜欢模式 3 而不是模式 1
Mode 1 is perfectly usable in many cases, and might be preferred over mode 3 in cases where assuming ownership would otherwise takes the form of moving the smart pointer to a local variable as in the reversed
example above. However, I can see two reasons to prefer mode 3 in the more general case:
模式 1 在许多情况下完全可用,并且在假设所有权将采用将智能指针移动到局部变量的形式的情况下,可能优于模式 3,reversed
如上例所示。但是,我可以看到在更一般的情况下更喜欢模式 3 的两个原因:
It is slightly more efficient to pass a reference than to create a temporary and nix the old pointer (handling cash is somewhat laborious); in some scenarios the pointer may be passed several times unchanged to another function before it is actually pilfered. Such passing will generally require writing
std::move
(unless mode 2 is used), but note that this is just a cast that does not actually do anything (in particular no dereferencing), so it has zero cost attached.Should it be conceivable that anything throws an exception between the start of the function call and the point where it (or some contained call) actually moves the pointed-to object into another data structure (and this exception is not already caught inside the function itself), then when using mode 1, the object referred to by the smart pointer will be destroyed before a
catch
clause can handle the exception (because the function parameter was destructed during stack unwinding), but not so when using mode 3. The latter gives the caller has the option to recover the data of the object in such cases (by catching the exception). Note that mode 1 here does not cause a memory leak, but may lead to an unrecoverable loss of data for the program, which might be undesirable as well.
传递一个引用比创建一个临时的和 nix 旧的指针更有效(处理现金有点费力);在某些情况下,指针可能会在实际被窃取之前多次原封不动地传递给另一个函数。这种传递通常需要写入
std::move
(除非使用模式 2),但请注意,这只是一个实际上不做任何事情的强制转换(特别是没有取消引用),因此它的附加成本为零。是否可以想象,在函数调用的开始和它(或某些包含的调用)实际将指向的对象移动到另一个数据结构的点之间,任何事物都会引发异常(并且该异常尚未在函数本身内部捕获) ),那么当使用模式 1 时,智能指针所指的对象将在
catch
子句可以处理异常之前被销毁(因为函数参数在堆栈展开期间被销毁),但使用模式 3 时则不然。后者给出了在这种情况下,调用者可以选择恢复对象的数据(通过捕获异常)。请注意,这里的模式 1不会导致内存泄漏,但可能会导致程序无法恢复的数据丢失,这也可能是不可取的。
Returning a smart pointer: always by value
返回智能指针:始终按值
To conclude a word about returninga smart pointer, presumably pointing to an object created for use by the caller. This is not really a case comparable with passing pointers into functions, but for completeness I would like to insist that in such cases always return by value(and don't usestd::move
in the return
statement). Nobody wants to get a referenceto a pointer that probably has just been nixed.
总结一个关于返回智能指针的词,大概指向一个为调用者使用而创建的对象。这并不是真正可以与将指针传递给函数相比的情况,但为了完整起见,我想坚持在这种情况下始终按值返回(并且不要std::move
在return
语句中使用)。没有人想要获得对可能刚刚被取消的指针的引用。
回答by Xeo
Yes you have to if you take the unique_ptr
by value in the constructor. Explicity is a nice thing. Since unique_ptr
is uncopyable (private copy ctor), what you wrote should give you a compiler error.
是的,如果您unique_ptr
在构造函数中使用by 值,则必须这样做。明确是一件好事。由于unique_ptr
不可复制(私有复制构造函数),您编写的内容应该会给您一个编译器错误。
回答by Omnifarious
Edit:This answer is wrong, even though, strictly speaking, the code works. I'm only leaving it here because the discussion under it is too useful. This other answer is the best answer given at the time I last edited this: How do I pass a unique_ptr argument to a constructor or a function?
编辑:这个答案是错误的,尽管严格来说,代码是有效的。我把它留在这里只是因为它下面的讨论太有用了。另一个答案是我上次编辑时给出的最佳答案:如何将 unique_ptr 参数传递给构造函数或函数?
The basic idea of ::std::move
is that people who are passing you the unique_ptr
should be using it to express the knowledge that they know the unique_ptr
they're passing in will lose ownership.
的基本思想::std::move
是,通过您的人unique_ptr
应该使用它来表达他们知道unique_ptr
他们将失去所有权的知识。
This means you should be using an rvalue reference to a unique_ptr
in your methods, not a unique_ptr
itself. This won't work anyway because passing in a plain old unique_ptr
would require making a copy, and that's explicitly forbidden in the interface for unique_ptr
. Interestingly enough, using a named rvalue reference turns it back into an lvalue again, so you need to use ::std::move
insideyour methods as well.
这意味着您应该在方法中使用对 a 的右值引用unique_ptr
,而不是 aunique_ptr
本身。这无论如何都行不通,因为传入一个普通的旧文件unique_ptr
需要制作一个副本,而这在unique_ptr
. 有趣的是,使用命名的 rvalue 引用会再次将其变回左值,因此您也需要::std::move
在方法内部使用。
This means your two methods should look like this:
这意味着您的两种方法应如下所示:
Base(Base::UPtr &&n) : next(::std::move(n)) {} // Spaces for readability
void setNext(Base::UPtr &&n) { next = ::std::move(n); }
Then people using the methods would do this:
然后使用这些方法的人会这样做:
Base::UPtr objptr{ new Base; }
Base::UPtr objptr2{ new Base; }
Base fred(::std::move(objptr)); // objptr now loses ownership
fred.setNext(::std::move(objptr2)); // objptr2 now loses ownership
As you see, the ::std::move
expresses that the pointer is going to lose ownership at the point where it's most relevant and helpful to know. If this happened invisibly, it would be very confusing for people using your class to have objptr
suddenly lose ownership for no readily apparent reason.
如您所见,the::std::move
表示指针将在最相关和最有帮助的地方失去所有权。如果这在无形中发生,那么使用您的类的人会objptr
突然失去所有权而没有明显的原因,这将是非常令人困惑的。
回答by Emilio Garavaglia
Base(Base::UPtr n):next(std::move(n)) {}
should be much better as
应该好得多
Base(Base::UPtr&& n):next(std::forward<Base::UPtr>(n)) {}
and
和
void setNext(Base::UPtr n)
should be
应该
void setNext(Base::UPtr&& n)
with same body.
同一个身体。
And ... what is evt
in handle()
??
而且......是什么evt
的handle()
?
回答by merito
To the top voted answer. I prefer passing by rvalue reference.
到最高投票的答案。我更喜欢通过右值引用传递。
I understand what's the problem about passing by rvalue reference may cause. But let's divide this problem to two sides:
我了解通过右值引用可能会导致什么问题。但是让我们把这个问题分成两个方面:
- for caller:
- 对于来电者:
I must write code Base newBase(std::move(<lvalue>))
or Base newBase(<rvalue>)
.
我必须编写代码Base newBase(std::move(<lvalue>))
或Base newBase(<rvalue>)
.
- for callee:
- 对于被叫方:
Library author should guarantee it will actually move the unique_ptr to initialize member if it want own the ownership.
库作者应该保证如果它想要拥有所有权,它实际上会移动 unique_ptr 来初始化成员。
That's all.
就这样。
If you pass by rvalue reference, it will only invoke one "move" instruction, but if pass by value, it's two.
如果通过右值引用传递,它只会调用一个“移动”指令,但如果通过值传递,它会调用两个。
Yep, if library author is not expert about this, he may not move unique_ptr to initialize member, but it's the problem of author, not you. Whatever it pass by value or rvalue reference, your code is same!
是的,如果图书馆作者不是这方面的专家,他可能不会移动 unique_ptr 来初始化成员,但这是作者的问题,而不是你。无论它是通过值还是右值引用传递,您的代码都是一样的!
If you are writing a library, now you know you should guarantee it, so just do it, passing by rvalue reference is a better choice than value. Client who use you library will just write same code.
如果你正在写一个库,现在你知道你应该保证它,所以就去做吧,通过右值引用传递是比值更好的选择。使用您库的客户只会编写相同的代码。
Now, for your question. How do I pass a unique_ptr argument to a constructor or a function?
现在,对于你的问题。如何将 unique_ptr 参数传递给构造函数或函数?
You know what's the best choice.
你知道什么是最好的选择。
http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html
http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html