我是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?
 


评论

相关:stackoverflow.com/q/3106110/46642和stackoverflow.com/questions/5481539/what-does-t-mean-in-c0x

在空指针上调用b1-> setNext时,这不是分段错误吗?

#1 楼

以下是将唯一指针作为参数的可能方法及其相关含义。

(A)按值

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中。

(B)通过非常量l值引用/>
必须在实际的l值(命名变量)上调用。不能用这样的临时变量来调用它:或可能不声明指针的所有权。给出以下代码:

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


不能保证nextBase为空。它可能是空的;可能不会。这实际上取决于Base::Base(std::unique_ptr<Base> &n)想要做什么。因此,仅从函数签名中就不会很明显了。您必须阅读实现(或相关文档)。

因此,我不建议将此作为接口。

(C)通过const l-value参考

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


我没有显示实现,因为您不能从const&移走。通过传递const&,您说的是该函数可以通过指针访问Base,但无法将其存储在任何位置。它无法声明其所有权。

这很有用。不一定适合您的特定情况,但是能够给某人一个指针并知道他们不能(没有破坏C ++的规则,就像不抛弃const一样)声明对它的所有权总是一件好事。他们无法存储它。他们可以将其传递给其他人,但是其他人必须遵守相同的规则。

(D)通过r值引用

Base newBase(nextBase);


这与“通过非常量l值引用”情况大致相同。区别是两件事。



您可以传递一个临时值:

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


确实是问题所在。如果看到以下行:

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


您有合理的期望,在此行完成之后,std::move应该为空。它应该已经从。毕竟,您有nextBase坐在那里,告诉您发生了移动。

问题是它没有发生。不保证已将其从中移出。它可能已经被移走了,但是您只有通过查看源代码才能知道。您不能仅从函数签名中看出。一个std::move,按值取值。

(C)通过const l值引用:如果您要使函数在函数执行期间仅使用unique_ptr,则请按unique_ptr取值。或者,将const&&传递给所指向的实际类型,而不要使用const&

(D)通过r值引用:如果一个函数可能要求所有权或不要求所有权(取决于内部代码路径),则请按unique_ptr对其进行取用。但是,我强烈建议不要在任何可能的情况下这样做。您只能移动它。正确的方法是使用标准库函数&&

如果按值取unique_ptr,则可以自由移动它。但是由于std::move,实际上没有发生移动。采取以下语句:

Base newBase(std::unique_ptr<Base>(new Base)); //legal now..


这实际上是两个语句:

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


(注:以上代码不会在技术上进行编译,因为非临时r值引用实际上不是r值。它仅用于演示目的。

unique_ptr只是对std::move的r值引用。移动发生在temporary的构造函数中。 oldPtr的move构造函数(本身带有newPtr的构造函数)是实际移动的内容。存储空间。

评论


@Nicol:但是std :: move没有命名其返回值。请记住,命名的右值引用是左值。 ideone.com/VlEM3

– R. Martinho Fernandes
2011年11月14日下午0:16

我基本上同意这个答案,但有一些意见。 (1)我认为没有有效的用例来传递对const左值的引用:被调用者可以执行的所有操作,也可以参考const(裸)指针,甚至可以更好地引用指针本身[和知道所有权是通过unique_ptr持有的,这无关紧要;也许其他一些调用者也需要相同的功能,但它们都持有shared_ptr。](2)如果所调用的函数修改了指针,例如从链接列表中添加或删除(列表拥有的)节点,则按左值引用调用可能会很有用。

– Marc van Leeuwen
2014-6-22 18:55



...(3)尽管您主张通过值传递而不是通过右值引用传递的说法很有意义,但我认为标准本身总是通过右值引用传递unique_ptr值(例如,将它们转换为shared_ptr时)。这样做的理由可能是效率更高(不执行移至临时指针的操作),同时它为调用者提供完全相同的权限(可以传递包装在std :: move的rvalue或lvalue,但不传递裸lvalue) 。

– Marc van Leeuwen
2014年6月22日18:58



只是重复Marc所说的话,并引用Sutter:“不要使用const unique_ptr&作为参数;而应使用widget *”

–乔恩
2014年7月29日在12:16



我们发现按值存在问题-移动发生在参数初始化期间,相对于其他参数评估,该移动是无序的(当然,在initializer_list中除外)。接受右值引用会强烈命令此移动发生在函数调用之后,因此在其他参数求值之后。因此,只要拥有所有权,就应该首选接受右值引用。

– Ben Voigt
2015年2月5日下午5:00

#2 楼

让我尝试说明将指针传递给对象的不同可行模式,这些对象的内存由std::unique_ptr类模板的实例管理。它也适用于较旧的std::auto_ptr类模板(我认为允许所有使用该唯一指针的模板,但是对于模板,在预期需要rvalue的情况下,还可以接受可修改的lvalue,而不必调用std::move),并且在某种程度上还作为讨论的具体示例,我将考虑以下简单列表类型

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


此类列表的实例(不能为允许与其他实例共享零件或循环使用)由拥有初始std::shared_ptr指针的人完全拥有。如果客户端代码知道存储的列表永远不会为空,则还可以选择直接存储第一个list而不是node
不需要定义list的析构函数:因为其字段的析构函数是自动的调用后,一旦初始指针或节点的生命周期结束,整个列表将被智能指针析构函数递归删除。

这种递归类型使您有机会讨论某些情况,这些情况在指向普通数据的智能指针。同样,函数本身有时也(递归地)提供客户端代码的示例。 node的typedef当然偏向list,但是可以将定义更改为使用unique_ptrauto_ptr,而无需过多更改为以下内容(特别是有关确保异常安全性而无需编写析构函数的信息)。 />
在周围传递智能指针的模式

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

如果您的函数与所有权无关,那么这是首选方法:不要使其完全采用智能指针。在这种情况下,您的函数无需担心谁拥有所指向的对象,或担心所有权的管理方式,因此传递原始指针既安全又是最灵活的形式,因为无论所有权如何,客户都可以始终产生原始指针(通过调用shared_ptr方法或从地址操作符get发出)。例如,用于计算此类列表长度的函数不应使用&参数,但是原始指针:

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


拥有变量list的客户端可以将此函数称为list head,而选择了存储length(head.get())的客户端表示一个非空列表可以调用node n。如果指针被确保为非null(此处不是这种情况,因为列表可能为空),则可能希望传递引用而不是a指针。如果函数需要更新节点的内容而不添加或删除任何内容(后者将涉及所有权),则它可能是指向非length(&n)的指针/引用。属于模式0类别的有趣情况是制作列表的(深层)副本;尽管执行此操作的功能当然必须转移其创建的副本的所有权,但它与正在复制的列表的所有权无关。因此可以定义如下:

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


这段代码值得仔细研究,以解决为什么要编译的问题(初始化初始化列表时对const进行递归调用的结果将绑定到copy的移动构造函数unique_ptr<node>中的右值引用参数)生成的listnext字段),以及为什么它是异常安全的(如果在递归分配过程中内存用完并且对node的某些调用抛出new,则此时指向部分构造的列表的指针为匿名保存在为初始化程序列表创建的std::bad_alloc类型的临时目录中,其析构函数将清除该部分列表)。顺便说一句,应该抵制将第二个list替换为nullptr的诱惑(就像我最初所做的那样),毕竟在那一点上它被认为是空的:一个人不能从(原始)指向常量的指针构造一个智能指针,即使

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

以智能指针值作为参数的函数将拥有所指向的对象立即:将调用者持有的智能指针(无论是在命名变量中还是在匿名临时对象中)复制到函数入口处的参数值中,并且调用者的指针已变为空(对于临时对象,副本可能已被删除,但无论如何,调用者都无法访问指向的对象)。我想通过现金呼叫此模式:呼叫者为所调用的服务付费,并且对呼叫后的所有权没有任何幻想。为了清楚起见,如果智能指针保存在变量中(从技术上讲,如果参数是左值),则语言规则要求调用者将参数包装在p中。在这种情况下(但不适用于下面的模式3),此函数将执行其名称所建议的操作,即将值从变量移到临时变量,而将变量保留为空。

对于被调用函数无条件获取指向对象的所有权(盗用)的情况,与std::movestd::unique_ptr一起使用的这种模式是将指针及其所有权传递到一起的好方法,这避免了任何内存泄漏的风险。尽管如此,我认为在很少的情况下,下面的模式3不会比模式1更受青睐。 (但是请参见下面的模式3的std::auto_ptr示例,其中说明了模式1至少也可以这样做。)如果该函数接受的参数不仅仅是该指针,则可能还有其他技术原因要避免模式1(使用reversedstd::unique_ptr):由于实际移动操作是在通过表达式std::auto_ptr传递指针变量p时发生的,因此不能假定std::move(p)在评估其他参数时保持有用的值(评估的顺序未指定) ,这可能导致细微的错误;相比之下,使用模式3可以确保在调用函数之前不会从p发生任何移动,因此其他参数可以通过p安全地访问值。一个单一的函数定义,它允许调用者在创建新的共享副本以供函数使用时选择是否为其自身保留指针的共享副本(这种情况在提供左值参数时发生;使用了共享指针的副本构造函数在调用时增加了引用计数),或者只是给函数提供了一个指针副本而不保留一个指针或不触及引用计数(在提供rvalue参数时可能会发生这种情况,可能是在调用p中包装了一个左值)。例如

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
}


通过分别定义std::shared_ptr(对于左值情况)和std::move(对于右值情况),可以实现相同的效果,而函数体的区别仅在于第一个版本调用复制语义(使用void f(const std::shared_ptr<X>& x)时使用复制构造/赋值),而第二个版本版本移动语义(如示例代码中那样编写void f(std::shared_ptr<X>&& x))。因此,对于共享指针,模式1可能有助于避免某些代码重复。

模式2:通过(可修改的)左值引用传递智能指针

在这里,该功能仅需要对智能指针进行可修改的引用,但没有说明如何使用它。我想通过卡调用此方法:调用者通过提供信用卡号来确保付款。引用可用于获取指向对象的所有权,但不必如此。此模式需要提供一个可修改的左值参数,这与以下事实有关:函数的期望效果可能包括在参数变量中保留有用的值。希望传递给该函数的带有右值表达式的调用方将被迫将其存储在一个命名变量中,以便能够进行调用,因为该语言仅提供了对常量左值引用的隐式转换(指的是临时值) )。 (与x处理的相反情况不同,不可能将其从std::move(x)转换为std::move,并使用Y&&为智能指针类型;不过,如果确实需要此转换可以通过简单的模板函数获得;请参见https://stackoverflow.com / a / 24868376/1436796)。对于被调用函数打算无条件获取对象所有权(从参数中窃取)的情况,提供左值参数的义务给出了错误的信号:变量在调用后将没有任何有用的值。因此,模式3可以在我们的函数中提供相同的可能性,但要求调用者提供一个右值,因此对于这种用法应该是首选。指针或以涉及所有权的方式指向的对象。例如,在节点前面加一个Y&的函数提供了这样的用法示例:它们的智能指针在调用后仍然拥有一个定义良好且非空的列表,尽管与以前不同。

再次有趣的是,观察到由于缺少可用内存而导致Y调用失败的情况。然后list调用将抛出std::move;在此时间点上,由于无法分配prepend,因此可以确定尚未对来自new的传递的右值引用(模式3)进行窃取,因为这样做是为了构造std::bad_allocnode域已分配。因此,当引发错误时,原始智能指针std::move(l)仍然保留原始列表;该列表将被智能指针析构函数正确销毁,或者如果由于足够早的next子句而使node幸免于难,它仍将保留原始列表。眨眼这个问题,也可以举一个更具破坏性的例子,删除包含给定值的第一个节点(如果有的话):

这里。值得注意的是,在最后一条语句中,要删除的节点中保存的指针ll(隐式)破坏该节点(当它破坏catch保留的旧值)之前被取消链接(通过(*p)->next,该指针返回指针,但使原始null) ),以确保此时仅破坏一个节点。 (在注释中提到的另一种形式中,此时间应留给release实例reset实例的移动分配运算符的实现内部执行;该标准表示20.7.1.2.3; 2该运算符应“按照

请注意,为本地非空列表存储本地p变量的客户端不能调用std::unique_ptrlist,这是正确的因此,由于给定的实现方式不适用于此类情况。

模式3:通过(可修改的)右值引用传递智能指针

当简单地获取指针所有权时,这是首选的模式。我想通过支票来调用此方法:调用者必须通过签署支票来接受放弃所有权,就像提供现金一样,但是实际提款被推迟到被调用函数实际窃取指针为止(与使用模式2时完全一样) )。 “检查签名”具体意味着,如果调用方是左值,则调用者必须在reset(u.release())中包装一个参数(如模式1)(如果是右值,则“放弃所有权”部分是显而易见的,不需要单独的代码) 。

请注意,从技术上讲,模式3的行为与模式2的行为完全相同,因此被调用函数不必承担所有权;但是,我坚持认为,如果在所有权转移方面存在任何不确定性(在正常使用情况下),则模式2应优先于模式3,这样,使用模式3隐含地向呼叫者表明他们正在放弃所有权。有人可能反驳说,只有模式1参数传递才真正向调用者发出强制丧失所有权的信号。但是,如果客户对被调用函数的用途有任何疑问,那么她应该知道被调用函数的规格,这可以消除任何疑问。

很难找到典型的例子涉及我们的使用模式3参数传递的prepend类型。一个典型的例子是将一个列表remove_first移到另一个列表node的末尾。但是,使用模式2更好地传递std::move(可以保留并保留操作结果):2:

void prepend (int x, list& l) { l = list( new node{ x, std::move(l)} ); }
模式
一个列表(及其所有权),并以相反的顺序返回包含相同节点的列表。本身,但反向列表也可以不同地使用。

在这里,该参数被立即移至局部变量以提高效率(可以直接在list处使用参数b,但是每次访问该参数都会涉及额外的间接调用);因此,与模式1参数传递的差异很小。实际上,使用该模式,该参数可以直接用作局部变量,从而避免了该初始操作。这只是一般原理的一个实例,如果通过引用传递的参数仅用于初始化局部变量,则也可以按值传递参数并将其用作局部变量。

该标准似乎提倡使用模式3,这是由以下事实证明的:所有提供的库函数都使用模式3转移了智能指针的所有权。一个特别令人信服的例子是构造函数a。该构造函数使用(在a中)获取可修改的左值引用(就像l = reversed(std::move(l));复制构造函数一样),因此可以像在l中那样使用p左值std::shared_ptr<T>(auto_ptr<T>&& p)进行调用,此后std::tr1已重置为null。由于参数传递从模式2更改为3,因此现在必须将此旧代码重写为auto_ptr<T>&,然后才能继续工作。我知道委员会不喜欢这里的模式2,但是他们可以选择定义为auto_ptr<T>来更改为模式1,因为它们可以确保旧代码无需修改就可以正常工作,因为(不同于唯一指针)自动指针可以静默地取消引用一个值(在过程中将指针对象本身重置为null)。显然,委员会比模式1更偏爱提倡模式3,以至于他们选择积极破坏现有代码,甚至不使用模式1甚至是已经弃用的用法。

何时比模式1更偏爱模式3

模式1在许多情况下都可以完美使用,并且在假设所有权的情况下,如以上p示例中那样将智能指针移动到局部变量的形式,它可能比模式3更可取。但是,在更一般的情况下,我看到有两个理由偏爱模式3的原因:


传递引用比创建临时指针和删除旧指针(处理)要更有效。现金有点费力);在某些情况下,在实际窃取指针之前,可能会将指针多次多次传递给另一个函数。通常,这样的传递需要编写std::shared_ptr<T> q(p)(除非使用模式2),但是请注意,这只是一个强制转换,实际上不执行任何操作(尤其是不进行取消引用),因此其成本为零。
应该可以想象,在函数调用的开始和它(或某些包含的调用)实际将指向对象移动到另一个数据结构的点之间引发任何异常(并且该异常尚未在函数本身内部捕获),然后使用模式1时,智能指针引用的对象将被销毁,然后p子句可以处理该异常(因为函数参数在堆栈展开时被破坏),但在使用模式3时则不会。在这种情况下(通过捕获异常)恢复对象数据的选项。请注意,此处的模式1不会导致内存泄漏,但可能导致程序数据无法恢复,这也是不希望的。

返回智能指针:始终按值

总结一下有关返回智能指针的信息,大概是指向创建供调用者使用的对象。这与将指针传递到函数中的情况实际上不具有可比性,但是出于完整性考虑,我要坚持在这种情况下始终按值返回(并且不要在std::shared_ptr<T> q(std::move(p))语句中使用std::shared_ptr<T>(auto_ptr<T> p))。没有人希望获得对可能刚刚被删除的指针的引用。

评论


模式0的+1-传递基础指针而不是unique_ptr。稍微偏离主题(因为问题是关于传递unique_ptr的问题),但它很简单,并且避免了问题。

–马赫塔
2014年7月18日在8:28

“此处的模式1不会导致内存泄漏”-表示模式3确实会导致内存泄漏,这是不正确的。无论unique_ptr是否已从其移出,只要销毁或重新使用它仍保留该值,它仍将很好地删除该值。

–rustyx
16年8月27日在10:10

@RustyX:我看不到您如何理解这种含意,并且我从未打算说出您的意思。我的意思是,与其他地方一样,unique_ptr的使用可以防止内存泄漏(因此在某种意义上可以履行其契约),但是在这里(即使用模式1),它可能会导致(在特定情况下)可能被认为更多的东西。有害的,即使用模式3可以避免的数据丢失(所指向的值被破坏)。

– Marc van Leeuwen
16年8月27日在10:36

#3 楼

是的,如果在构造函数中按值取unique_ptr,则必须这样做。明确是一件好事。由于unique_ptr是不可复制的(私有副本ctor),因此您编写的内容将给您带来编译器错误。

#4 楼

编辑:这个答案是错误的,即使严格来讲,代码也可以。我仅将其留在此处,因为其下的讨论太有用了。这是我上次编辑此内容时给出的最佳答案:如何将unique_ptr参数传递给构造函数或函数? ::std::move应该使用它来表达他们知道所传递的unique_ptr会失去所有权的知识。

这意味着您应该在方法中使用对unique_ptr的右值引用,而不是unique_ptr本身。无论如何这将不起作用,因为传入普通的旧unique_ptr需要进行复制,并且在unique_ptr的界面中明确禁止这样做。有趣的是,使用命名的rvalue引用会将其再次转换为左值,因此您还需要在方法内部使用unique_ptr

这意味着您的两个方法应如下所示:

Base(Base::UPtr &&n) : next(::std::move(n)) {} // Spaces for readability

void setNext(Base::UPtr &&n) { next = ::std::move(n); }


然后使用该方法的人们会这样做:在最相关和最有帮助的时候会失去所有权。如果这是无形的事情发生,那么使用您的班级的人会因为没有显而易见的原因而突然失去::std::move,这将非常令人困惑。

评论


命名的右值引用是左值。

– R. Martinho Fernandes
2011年11月13日在20:38

您确定它是Base fred(:: std :: move(objptr));而不是Base :: UPtr fred(:: std :: move(objptr)); ?

– codablank1
2011年11月13日在20:41

要添加到我之前的评论中:此代码将无法编译。您仍然需要在构造函数和方法的实现中都使用std :: move。即使您按值传递,调用方仍必须使用std :: move传递左值。主要区别在于,通过值传递该接口可以清楚地知道所有权将丢失。参见Nicol Bolas对另一个答案的评论。

– R. Martinho Fernandes
2011年11月13日20:45



@ codablank1:是的。我正在演示如何在采用右值引用的base中使用构造函数和方法。

–恶毒
2011年11月13日在20:45

@ R.MartinhoFernandes:噢,有趣。我认为这是有道理的。我原以为您是错的,但实际测试证明您是正确的。立即修复。

–恶毒
2011年11月13日在20:52

#5 楼

tl; dr:请勿使用unique_ptr这样的代码。
我相信您的情况太糟了-对于那些需要阅读,维护您的代码的人,以及可能需要使用它的人。 br />
如果公开公开了unique_ptr成员,则只能使用unique_ptr构造函数参数。它们非常适合本地使用-不好,实际上也不打算用于接口。要界面吗?将新课程记录为拥有所有权,并让其获取原始资源;或在使用指针的情况下,请按照《核心指南》中的建议使用unique_ptr

不要暴露您在内部使用owner<T*>的事实

unique_ptr用于列表节点实际上是一个实现细节。实际上,即使您只是让类列表机制的用户直接使用裸列表节点(自己构造并提供给您)这一事实也不是恕我直言。我不需要形成一个新的列表节点,它也是一个列表,也可以添加一些东西到您的列表中-我只需要按值,const左值ref和/或右值ref传递有效负载。然后,您处理它。而对于拼接列表-同样,值,常量左值和/或右值。

评论


尽管关于语言的问题和能力有很多非常有用的答案,但是这个答案的重点是如此重要。谢谢。

–里卡多·克里斯蒂安·拉米雷斯(Ricardo Cristian Ramirez)
9月14日20:17

#6 楼

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()是什么???

评论


在此处使用std :: forward没有任何好处:Base :: UPtr &&始终是右值引用类型,而std :: move将其作为右值传递。已经正确转发了。

– R. Martinho Fernandes
2011年11月13日20:17



我非常不同意。如果一个函数按值采用unique_ptr,那么可以保证在新值上调用了一个移动构造函数(或者只是给了您一个临时值)。这样可以确保用户现在拥有的unique_ptr变量为空。如果您用&&代替,则只有在您的代码调用了move操作时,它才会被清空。用您的方式,可能不必删除用户的变量。这使用户对std ::: mo的使用变得令人怀疑和困惑。使用std :: move应该始终确保已移动了某些内容。

–尼科尔·波拉斯(Nicol Bolas)
2011年11月13日在20:19

@NicolBolas:你是对的。我将删除我的答案,因为当它起作用时,您的观察是绝对正确的。

–恶毒
2011年11月13日在20:57

#7 楼

到最高投票答案。我更喜欢通过右值引用传递。

我知道通过右值引用传递可能导致什么问题。但是让我们将这个问题分为两个方面:


对于呼叫者:

我必须编写代码Base newBase(std::move(<lvalue>))Base newBase(<rvalue>)


对于被调用者:

如果想要拥有所有权,库作者应该保证它将实际上移动unique_ptr初始化成员。

就这样。

如果通过右值引用传递,它将仅调用一条“移动”指令,但是如果通过值传递,则为两个。移动unique_ptr初始化成员,但这是作者的问题,而不是您的问题。无论通过值或右值引用传递什么,您的代码都是一样的!

如果您正在编写一个库,现在您知道您应该保证它,所以只需这样做,通过右值引用传递是一个更好的选择比价值。使用您的库的客户端将只编写相同的代码。

现在,您的问题得到解决。如何将unique_ptr参数传递给构造函数或函数?

您知道什么是最佳选择。

http://scottmeyers.blogspot.com/2014/07 /should-move-only-types-ever-be-passed.html