我如何传递的unique_ptr参数构造函数或函数?

我是新来的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?

--------------解决方案-------------

这里是采取独特指针作为参数,以及它们相关的含义的可能方式。

按值

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&&这是一个r值参考nextBase 。 这就是它。

由于Base::Base(std::unique_ptr<Base> n)通过值而不是通过r值参考接受其参数,C ++会自动建立一个临时的我们。 它创建std::unique_ptr<Base>Base&& ,我们通过给函数std::move(nextBase) 正是这种临时性的实际移动距离值的结构nextBase入函数参数n

通过非const左值参考

Base(std::unique_ptr<Base> &n)
: next(std::move(n)) {}

这对一个实际的左值(一个命名的变量)被调用。 它不能用临时像这样被称为:

Base newBase(std::unique_ptr<Base>(new Base)); //Illegal in this case.

这样做的意义是一样的任何其他用途非const引用的含义是:功能可能会或可能不会声称指针的所有权。 鉴于这样的代码:

Base newBase(nextBase);

有没有保证nextBase是空的。 可能是空的; 也可能不是。 这真的取决于什么Base::Base(std::unique_ptr<Base> &n)想做的事。 正因为如此,它不是很明显只是从功能上签名什么事情要发生; 你必须阅读的实现(或相关文件)。

正因为如此,我不建议这是一个接口。

以const L值参考

Base(std::unique_ptr<Base> const &n);

我没有表现的实现,因为你无法从移动const& 。 通过传递const& ,你说该函数可以访问该Base通过指针,但不能其存储在任何地方。 它不能声称它的所有权。

这可能是有用的。 不一定适合您的具体情况,但它总是好的就能到手某人指针和知道他们不能(不破坏C ++的规则,像没有虚掷const )它主张所有权。 他们不能存储。 他们可以将它传递给其他人,但那些人都遵守同样的规则遵守。

由R值参考

Base(std::unique_ptr<Base> &&n)
: next(std::move(n)) {}

这是为了在“通过非const左值参照”的情况下或多或少是相同的。 差异是两件事情。

  1. 可以通过一个暂时的:

    Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
  2. 必须使用std::move通过非暂时性的参数时。

后者是真正的问题。 如果您看到这行:

Base newBase(std::move(nextBase));

你有一个合理的预期是,该行完成后, nextBase应该是空的。 它应该已经从移动。 毕竟,你有std::move坐在那里,告诉你发生了移动。

的问题是,它也没有。 不能保证已经从移动。 它可能是从感动,但你只能通过查看源代码知道了。 你不能只从函数签名告诉。

因此,我的建议是这样的:

  • 如果你的意思声称一个函数到所有权 unique_ptr ,把它按值。
  • 如果你的意思是一个功能简单使用unique_ptr该函数的执行期间,把它由const& 。 可替代地,通过一个&const&实际类型指出,而不是使用unique_ptr
  • 如果一个函数可能会或可能不会主张所有权(取决于内部的代码路径),然后把它用&& 。 但我强烈反对任何可能这样做。

如何操纵的unique_ptr

你不能复制unique_ptr 。 您只能移动它。 要做到这一点,正确的方法是用std::move标准库函数。

如果拍摄unique_ptr通过值,您可以从中自由移动。 但运动实际上并没有因为发生std::move 。 看看下面的语句:

std::unique_ptr<Base> newPtr(std::move(oldPtr));

这实际上是两个语句:

std::unique_ptr<Base> &&temporary = std::move(oldPtr);
std::unique_ptr<Base> newPtr(temporary);

(注:上面的代码在技术上不编译,因为非暂时性的R值的引用是不实际的r值正是在这里仅用于演示目的)。

temporary只是一个r值参照oldPtr 。 它是在构造函数中newPtr所在的运动情况。 unique_ptr的移动构造函数(构造函数,需要一个&&本身)是进行实际运动。

如果你有一个unique_ptr价值,要存储在某个地方你必须使用std::move做存储。

让我试图说明各地传递对象的指针,其内存由一个实例管理的不同可行模式std::unique_ptr类模板; 它也适用于旧std::auto_ptr类模板(我相信允许所有使用了独特的指针呢,但它除了修改左值将被接受,其中右值预期,而无需调用std::move ),并在一定程度上也以std::shared_ptr

作为一个具体的例子为讨论我会考虑下面这个简单的列表类型

struct node;
typedef std::unique_ptr<node> list;
struct node { int entry; list next; }

该名单(不能被允许与其他实例零件或圆形)的情况下,完全由谁保持初始拥有list指针。 如果客户代码知道列表它存储将永远是空的,它也可以选择以存储所述第一node直接而不是一个list 。 没有析构函数node需要定义:因为它的领域析构函数都会被自动调用,整个列表将被递归智能指针的析构函数一次初始指针或节点的寿命结束删除。

这种递归式给出的场合来讨论某些情况下,是在一个智能指针到普通数据的情况下不可见。 另外,函数本身偶尔提供(递归)的客户端代码的例子也是如此。 对于typedef的list当然是对偏见的unique_ptr ,但定义可以改为使用auto_ptrshared_ptr ,而不是没有太多需要改变什么,据说下面(特别是关于异常安全,而不需要写析构函数得到保证)。

周围路过智能指针方式

模式0:传递一个指针或引用参数,而不是智能指针

如果你的函数没有与所有权而言,这是首选的方法:不要让它走智能指针都没有。 在这种情况下,你的功能并不需要担心谁拥有指向的对象,或者通过什么途径所有权进行管理,因此通过原始指针既绝对安全,最灵活的方式,因为无论其所有权的客户可以随时产生一个原始指针(通过调用get方法或从地址运算符& )。

例如,该函数来计算该列表的长度,不应该是给一个list参数,但原始指针:

size_t length(const node* p)
{ size_t l=0; for ( ; p!=nullptr; p=p->next.get()) ++l; return l; }

保存一个变量客户端list head可以调用这个函数length(head.get())而选择,而不是客户端存储node n表示非空单可致电length(&n)

如果指针是保证非空(这不是这种情况在这里,因为列表可以是空的)一种可能更喜欢通过一个参考,而不是一个指针。 它可能是一个指针/参考非const如果函数需要更新节点(S)的内容,无需添加或删除其中的任何(后者将涉及所有权)。

在模式0类属于一个有趣的案例是上榜的(深)复印件; 而功能做这必须在创建副本的过程所有权的转让,它并不关心它复制列表的所有权。 因此它可以被定义如下:

list copy(const node* p)
{ return list( p==nullptr ? nullptr : new node{p->entry,copy(p->next.get())} ); }

此代码值得密切关注,都为这个问题,为什么它编译(递归调用copy在初始化器列表绑定到的拷贝构造右值引用参数unique_ptr<node> ,又名list )和问题作为为什么它是异常安全(如果在递归分配过程内存用完以及一些调用new抛出std::bad_alloc ,然后在那个时候一个指向部分构造列表匿名在临时类型的持有list的创建在初始化器列表,它的析构函数会清理部分列表)。 顺便说一句应该抵制诱惑,以取代(如我最初做的)第二nullptrp ,这毕竟是已知在这一点空:一个人不能从一个(原始) 指针不断构建一个智能指针,甚至当它被称为是空。

模式1:按值传递智能指针

这需要一个智能指针值作为参数的函数占有的对象指向马上:主叫方举行的智能指针(无论是在命名变量或匿名临时)复制到在函数入口参数值和调用者的指针已成为空(在一个临时副本可能已省略的情况下,但在任何情况下,主叫方已失去访问指向的对象)。 我想用现金把这种模式的呼叫 :主叫方支付了前面的称为服务,并能没有关于呼叫后所有权幻想。 为了更清楚,语言规则要求调用者包裹在参数std::move如果智能指针是一个变量(在技术上,如果该参数是一个左值)举行; 在这种情况下(但不低于模式3)这个函数做什么它的名字所暗示的,即从该变量的值移到临时,留下变量空。

对于情况下调用的函数无条件接受(pilfers)的所有权所指向的对象,而且使用这种模式std::unique_ptrstd::auto_ptr是一起传递指针,其所有权,从而避免任何风险的好方法内存泄漏。 不过我认为,只有极少数的情况下模式低于3不应在模式1(非常轻微)择优为此,我应提供这种模式没有使用例子。

当使用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
}

同样可以通过分别定义来实现void f(const std::shared_ptr<X>& x)为左值的情况下)和void f(std::shared_ptr<X>&& x)为右值的情况下),与只有在不同的函数体的第一个版本复制调用语义(当使用使用拷贝构造/分配x ),但第二个版本移动语义(写std::move(x)来代替,如示例代码)。 因此,对于共享指针,模式1可避免一些重复代码非常有用。

模式2:路过(修改)左值引用一个智能指针

这里的功能只需要有一个修改的参考智能指针,但没有给出什么它会用它做指示。 我想通过卡来调用这个方法调用:来电者通过给信用卡号码,确保付款。 基准可以被用来取的所有权指向的对象,但它不必。 该模式要求提供一个修改的左值的参数,对应于该事实,即函数的所需效果可以包括留在参数变量一个有用的值。 与右值表达式,它希望通过这样一个函数的调用者将被迫将其存储在一个名为变量能够拨打电话,因为语言只提供隐式转换恒定左值引用(指临时)从右值。 (不同于由处理的情况正好相反std::move ,从铸造Y&&Y&Y共享指针类型,是不可能的;但是这种转换可以通过一个简单的模板功能,如果真的希望获得;请参阅http:// stackoverflow.com/a/24868376/1436796)。 对于其中调用函数拟无条件取对象的所有权,从参数窃取的情况下,有义务提供一个左值参数被赋予了错误的信号:变量将具有呼叫后没有使用价值。 因此模式3,这给我们的函数中相同的可能性,但询问来电者提供一个右值,应首选这种用法。

然而,有一个有效的用例模式2,即可能会修改指针或该对象在涉及所有权的方式指向功能。 例如,该前缀节点到一个功能list提供了这样使用的例子:

void prepend (int x, list& l) { l = list( new node{ x, std::move(l)} ); }

(同样有趣的是,观察会发生什么,如果new调用抛出std::bad_alloc :因为new失败,它还不能有被盗的原始list对象构造next子虚乌有的领域node ,所以原来的智能指针对象仍然保持着原来的列表,该列表要么得到妥善的智能指针的析构函数毁坏,或智能指针对象生存多亏了足够早catch ,它仍然会保持原来的列表),显然,这是不可取这里给力。呼叫者使用std::move ,因为他们的智能指针仍拥有呼叫后一个明确的和非空列表,但不同的人比以前。

这是一个建设性的例子; 用眨眼这个问题人们也可以得到除去第一个节点包含一个给定值,如果任一更具破坏性例如:

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);
}

同样的正确性在这里相当微妙; 尤其是指针(*p)->next举行不被删除的节点内未链接(通过release ,它返回指针,但使得原本空) 之前 reset (隐含的)破坏该节点上,确保且仅有一个节点销毁。 (在备选方案中所指出的,这时间将留给的布展赋值运算符的执行内部std::unique_ptr实例list ;标准说20.7.1.2.3; 2,该经营者应当采取行动“,仿佛通过调用reset(u.release())从那里的时机应该是安全的这里。)

请注意,这些功能不能用谁存储的本地客户端调用node变量总是一个非空列表,这是正确的,因为给出的实现不会在那里工作。

模式3:路过(修改)右值引用一个智能指针

这是当简单地把指针的所有权使用的优选模式。 我想用支票来调用这个方法调用 :调用者必须接受放弃所有权,因为如果提供现金,通过签署支票,但实际撤军推迟到被调用函数实际上pilfers指针(正是因为它会在使用模式2 )。 在“支票签署”具体是指呼叫者必须包装在一个参数std::move (如在模式1),如果它是一个左值(如果它是一个右值时,“放弃所有权”部分是显而易见的,不需要单独的代码)。

需要注意的是在技术上模式3的行为完全作为模式2,所以被调用的函数不必假设所有权; 不过,我还是坚持认为,如果有关于所有权转让(在正常使用)任何不确定性,模式2应首选模式3,因此使用模式3是隐含的信号给调用者他们放弃所有权。 有人可能会反驳说,只有模式1参数传递信号确实向呼叫者所有权被迫丢失。 但是,如果一个客户有大约被调用函数的意图有任何怀疑,她应该知道被调用的函数的规格,这应该消除任何疑虑。

这是令人惊讶的很难找到涉及我们一个典型的例子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
}

模式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;
}

这个功能可以被称为如在l = reversed(std::move(l));扭转列表入本身,但也可以不同的方式用于反向列表。

这里的论点是立即采取行动,对效率的局部变量(一个可能的参数使用l直接的地方p ,但每次都将涉及额外的间接级别访问它); 因此与模式1的参数传递的差异很小。 在使用该模式其实,这种观点可能直接担任局部变量,从而避免了最初的感动; 这只是一般原则的一个实例,如果按引用传递的参数仅用于初始化一个局部变量,人们还不如按值传递它,而不是和使用参数为局部变量。

使用模式3似乎是由标准的提倡,由事实提供的所有库函数使用传输模式3.点一个特别有说服力的情况下,智能指针的所有权是构造作为见证std::shared_ptr<T>(auto_ptr<T>&& p) 使用(在构造函数中std::tr1 )采取修改的左值引用(就像auto_ptr<T>&拷贝构造函数),因此可以用所谓auto_ptr<T>左值pstd::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甚至是一个已经过时的使用。

当喜欢模式3比模式1

模式1是在许多情况下完全可用,并且可能是优选的过模式3在假设所有权否则需要移动智能指针到本地变量如在的形式的情况下reversed上面的例子。 不过,我可以看到两个原因喜欢模式3中更一般的情况:

  • 它会更有效传递一个参考,而不是建立一个临时的,尼克斯旧指针(处理现金有点费力); 在某些情况下指针可以传递多次改变到另一个功能之前,它实际上是盗窃。 这样的传球一般需要写std::move (除非模式2时),但是请注意,这只是一个演员,其中实际上并没有做任何事情(尤其是无解引用),所以它有附加零成本。
  • 它应该是可以想象的东西抛出函数调用的开始和点之间的异常的地方(或包含通话)实际上移动指向的对象到另一个数据结构(这个异常没有被函数本身抓到) ,然后用模式1由智能指针所指的对象将在函数返回时(因为该参数的寿命结束时),但并非如此使用模式3时,后者给调用者有以恢复数据的选项被破坏(通过捕获除外)的对象。 注意,模式1在这里不会导致内存泄漏 ,但可能导致数据的方案,这可能是不希望的,以及一个不可恢复的损失。

按值始终:返回一个智能指针

结束有关返回智能指针,想必指向由调用者使用创建的对象的单词。 这是不是真的与传递指针到功能可比的情况下,但为了完整,我想坚持认为,在这种情况下总是返回值 (和不使用 std::movereturn语句)。 没有人愿意去大概刚刚nixed一个指针引用

是的,你有,如果你拿unique_ptr在构造函数中按值。 明确地是一件很好的事。 由于unique_ptr是不可复制(私有拷贝构造函数),你写的应该给你一个编译器错误。

编辑:这答案是错的,虽然严格来说,代码工作。 我只是离开这里,因为它下面的讨论实在太有用了。 这对方的回答是我最后编辑这篇文章的时候给出最好的答案:如何传递的unique_ptr作为构造函数或方法的参数?

的基本思路::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突然失去所有权没有显而易见的原因。

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)

与同一机构。

而...什么是evthandle()

分类:C# 时间:2015-03-16 人气:291
本文关键词: 参数,C ++ 11,独特的PTR
分享到:

相关文章

Copyright (C) 55228885.com, All Rights Reserved.

55228885 版权所有 京ICP备15002868号

processed in 0.917 (s). 11 q(s)