我是新手在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?
答案 0 :(得分:759)
以下是将唯一指针作为参数的可能方法,以及它们的相关含义。
Base(std::unique_ptr<Base> n)
: next(std::move(n)) {}
为了让用户调用它,他们必须执行以下操作之一:
Base newBase(std::move(nextBase));
Base fromTemp(std::unique_ptr<Base>(new Base(...));
按值获取唯一指针意味着您传输指向所讨论的函数/对象/等的指针的所有权。构建newBase
后,nextBase
保证为空。你不拥有这个对象,你甚至没有指向它的指针。它消失了。
这是可靠的,因为我们按值获取参数。 std::move
实际上并没有移动任何东西;这只是一个花哨的演员。 std::move(nextBase)
会返回Base&&
,这是对nextBase
的r值引用。就是这样。
因为Base::Base(std::unique_ptr<Base> n)
通过值而不是r值引用来获取其参数,所以C ++将自动为我们构造临时值。它会从我们通过std::unique_ptr<Base>
提供功能的Base&&
创建std::move(nextBase)
。实际上,将nextBase
中的值移动到函数参数n
中是构造此临时值。
Base(std::unique_ptr<Base> &n)
: next(std::move(n)) {}
必须在实际的l值(命名变量)上调用它。它不能像这样暂时调用:
Base newBase(std::unique_ptr<Base>(new Base)); //Illegal in this case.
这个含义与非const引用的任何其他用法的含义相同:该函数可能或者可能不声明指针的所有权。鉴于此代码:
Base newBase(nextBase);
无法保证nextBase
为空。 可能为空;它可能不会。这实际上取决于Base::Base(std::unique_ptr<Base> &n)
想要做什么。因此,从功能签名中发现的事情并不是很明显。你必须阅读实现(或相关文档)。
因此,我不建议将其作为界面。
Base(std::unique_ptr<Base> const &n);
我没有显示实现,因为无法从const&
移动。通过传递const&
,您说该函数可以通过指针访问Base
,但它不能存储它在任何地方。它不能声称拥有它。
这可能很有用。不一定适用于您的具体情况,但能够向某人发送指针并知道他们不能(不破坏C ++规则,如不会抛弃const
)声明所有权总是好的它的。他们无法存储它。他们可以将它传递给其他人,但其他人必须遵守相同的规则。
Base(std::unique_ptr<Base> &&n)
: next(std::move(n)) {}
这或多或少与“非常量l值参考”情况相同。差异是两件事。
你可以传递一个临时的:
Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
在传递非临时参数时,必须使用std::move
。
后者确实是问题所在。如果你看到这一行:
Base newBase(std::move(nextBase));
您有合理的期望,在此行完成后,nextBase
应为空。它本应该被移走。毕竟,你有std::move
坐在那里,告诉你已经发生了移动。
问题在于它没有。它不是保证。 可能已被移除,但您只能通过查看源代码来了解。你无法从功能签名中分辨出来。
unique_ptr
的所有权,请按值进行。unique_ptr
,请按{{1} }。或者,将const&
或&
传递给指向的实际类型,而不是使用const&
。unique_ptr
进行操作。但我强烈建议尽可能不这样做。您无法复制&&
。你只能移动它。正确的方法是使用unique_ptr
标准库函数。
如果按值std::move
,您可以自由地移动它。但由于unique_ptr
,实际上并没有发生运动。请听以下声明:
std::move
这实际上是两个陈述:
std::unique_ptr<Base> newPtr(std::move(oldPtr));
(注意:上面的代码在技术上并不编译,因为非临时r值引用实际上不是r值。它仅用于演示目的。)
std::unique_ptr<Base> &&temporary = std::move(oldPtr);
std::unique_ptr<Base> newPtr(temporary);
只是对temporary
的r值引用。它位于oldPtr
的构造函数中,其中发生了移动。 newPtr
的移动构造函数(一个将unique_ptr
带到自身的构造函数)就是实际的移动。
如果您有&&
值且想要将其存储在某处,则必须使用unique_ptr
进行存储。
答案 1 :(得分:4)
是的,如果您在构造函数中使用unique_ptr
by值,则必须这样做。明确是一件好事。由于unique_ptr
是不可复制的(私有拷贝ctor),所写的内容应该会给你一个编译错误。
答案 2 :(得分:3)
编辑:这个答案是错误的,即使严格来说,代码仍然有效。我只是把它留在这里因为它下面的讨论太有用了。这个答案是我上次编辑时给出的最佳答案:How do I pass a unique_ptr argument to a constructor or a function?
::std::move
的基本想法是,unique_ptr
传递给您的人应该使用它来表达他们知道他们传入的unique_ptr
将失去所有权的知识
这意味着您应该在方法中使用unique_ptr
的右值引用,而不是unique_ptr
本身。这无论如何都不会起作用,因为传入一个普通的旧unique_ptr
需要复制,而unique_ptr
的接口中明确禁止复制。有趣的是,使用命名的右值引用会将其再次转换为左值,因此您还需要在方法中使用::std::move
。
这意味着您的两种方法应如下所示:
Base(Base::UPtr &&n) : next(::std::move(n)) {} // Spaces for readability
void setNext(Base::UPtr &&n) { next = ::std::move(n); }
然后使用这些方法的人会这样做:
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
如您所见,::std::move
表示指针在最相关且最有帮助的地方将失去所有权。如果发生这种情况,那么使用你的班级让objptr
突然失去所有权的人就会非常困惑。
答案 3 :(得分:2)
unique_ptr
。我相信您正在惹得一团糟-对于那些需要阅读,维护您的代码的人,也许还有那些需要使用它的人。
unique_ptr
成员,则仅获取unique_ptr
构造函数参数。 unique_ptr
包装用于所有权和生命周期管理的原始指针。它们非常适合本地使用-不好,实际上也不打算用于接口。要界面吗?将新课程记录为拥有所有权,并让其获取原始资源;或者,对于指针,可以按照{{3}}中的建议使用owner<T*>
。
仅当您的类的目的是持有unique_ptr
的类,并且让其他人照原样使用这些unique_ptr
时-才是您的构造函数或方法采用它们的合理性。
unique_ptr
的事实在列表节点上使用unique_ptr
是一个实现细节。实际上,即使您只是让类列表机制的用户直接使用裸列表节点(自己构造并提供给您)这一事实,恕我直言也不是好主意。我不需要形成一个新的列表节点,它也是一个列表,也可以将一些东西添加到您的列表中-我只需要按值,const左值ref和/或右值ref传递有效负载即可。然后,您处理它。对于拼接列表-同样,值,常量左值和/或右值。
答案 4 :(得分:0)
Base(Base::UPtr n):next(std::move(n)) {}
应该好多了
Base(Base::UPtr&& n):next(std::forward<Base::UPtr>(n)) {}
和
void setNext(Base::UPtr n)
应该是
void setNext(Base::UPtr&& n)
同一个身体。
而且...... evt
中的handle()
是什么