如何将unique_ptr参数传递给构造函数或函数?


400

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


1
当您在空指针上调用b1-> setNext时,这不是分段错误吗?
巴尔基

Answers:


836

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

(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&&作为r值引用的nextBase。这就是全部。

因为Base::Base(std::unique_ptr<Base> n)它的参数是通过值而不是r值引用,所以C ++会自动为我们构造一个临时变量。它通过std::unique_ptr<Base>从中创建了一个Base&&,我们通过提供了该功能std::move(nextBase)。正是这个临时结构的构造实际上值从中nextBase移到了函数参数中n

(B)通过非常量L值引用

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)要做什么。因此,仅从函数签名中就不会很明显了。您必须阅读实现(或相关文档)。

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

(C)通过const l值引用

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

我没有显示实现,因为您不能从移出const&。通过传递一个const&,您表示该函数可以Base通过指针访问,但不能存储在任何地方。它不能要求它的所有权。

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

(D)按r值参考

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

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

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

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

后者确实是问题。如果看到此行:

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

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

问题在于它还没有。不保证已将其从中移出。它可能已从中移出,但是您只有通过查看源代码才能知道。您不能仅从函数签名中分辨出来。

推荐建议

  • (A)按价值:如果您要让一个函数声明对a的所有权unique_ptr,请按价值来接受它。
  • (C)通过const l值引用:如果您要在某个函数unique_ptr执行期间使用一个简单的函数,则使用const&。或者,将&或传递给所const&指向的实际类型,而不是使用unique_ptr
  • (D)通过r值引用:如果一个函数可能要求所有权,也可能不要求所有权(取决于内部代码路径),则按&&。但是我强烈建议不要在可能的情况下这样做。

如何操作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。它位于运动发生位置的构造函数newPtrunique_ptr的move构造函数(&&本身带有a的构造函数)是实际的运动。

如果您有一个unique_ptr价值,并且想要将其存储在某个地方,那么您必须使用std::move该值进行存储。


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

31
我基本上同意这个答案,但有一些意见。(1)我认为没有有效的用例来传递对const左值的引用:被调用者可以做的所有事情,也可以参考const(裸)指针,甚至更好的是指针本身[和知道所有权在整个过程中都没有关系unique_ptr;也许其他一些调用者也需要相同的功能,但是要保留一个shared_ptr替代项](2)如果被调用函数修改了指针,例如,从链接列表中添加或删除(列表拥有的)节点,则按左值引用进行调用可能会很有用。
Marc van Leeuwen 2014年

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

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

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

57

让我尝试说明将指针传递给对象的各种可行模式,这些对象的内存由std::unique_ptr类模板的实例管理。它也适用于较老的std::auto_ptr类模板(我认为允许所有使用唯一指针的模板,但是在需要rvalue的情况下,无需调用也可以接受可修改的lvalue 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方法或从address-of运算符&)。

例如,用于计算此类列表长度的函数不应提供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)

如果保证指针为非null(此处不是这种情况,因为列表可能为空),则可能希望传递引用而不是指针。const如果函数需要更新节点的内容而不增加或删除其中的任何内容(后者将涉及所有权),则它可能是指向非对象的指针/引用。

属于模式0类别的一个有趣情况是制作列表的(深层)副本。虽然执行此功能的功能当然必须转移其创建的副本的所有权,但它与正在复制的列表的所有权无关。因此可以定义如下:

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

这段代码值得仔细一看,以解决为什么它根本不能编译的问题(初始化初始化字段时,copy初始化列表中对的递归调用结果绑定到move构造函数unique_ptr<node>aka 的右值引用参数上)。生成的),以及一个问题,为什么它是异常安全(如果在递归分配过程内存用完,有些通话的抛出,然后在该时间的指针部分构成列表匿名在临时类型的举行为初始化程序列表创建,其析构函数将清理该部分列表)。顺便说一个人应该抵制诱惑,以取代(正如最初我)第二个通过listnextnodenewstd::bad_alloclistnullptrp,毕竟在那一点上它已知为null:即使已知它为null,也无法从(原始)指针构造一个智能指针指向constant

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

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

对于被调用函数无条件获得指向对象的所有权(盗用)的情况,此模式与一起使用std::unique_ptr或是std::auto_ptr将指针及其所有权传递到一起的好方法,这避免了任何内存泄漏的风险。尽管如此,我认为在很少的情况下,下面的模式3不会比模式1更受青睐(出于某种原因)。因此,我将不提供该模式的使用示例。(但是请参见reversed下面的模式3 的示例,其中说明了模式1至少也可以做到。)如果函数接受的参数不仅仅是该指针,那么可能还会有避免该模式技术原因1(带有std::unique_ptrstd::auto_ptr):由于在传递指针变量时发生了实际的移动操作p该表达式std::move(p),不能假定p在评估其他参数时具有有用的价值(未指定评估顺序),这可能会导致细微的错误;相比之下,使用模式3可以确保p在函数调用之前不会发生任何移动,因此其他参数可以通过来安全地访问值p

当与一起使用时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:通过(可修改的)左值引用传递智能指针

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

但是,模式2有一个有效的用例,即可以修改指针或以涉及所有权的方式指向的对象的函数。例如,将节点前缀为a的函数list提供了此类用法的示例:

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

显然,在这里强制调用者使用是不希望的std::move,因为他们的智能指针在调用之后仍然拥有一个定义良好且非空的列表,尽管与之前的列表不同。

再次有趣的是,观察prepend由于缺少可用内存而导致调用失败的情况。然后new电话会抛出std::bad_alloc; 在这一时间点上,由于node无法分配,因此可以确定从那里传递来的右值引用(模式3)std::move(l)尚未被窃取,因为这样做可以构造未能分配的next字段node。因此,l当引发错误时,原始智能指针仍将保留原始列表。该列表将被智能指针析构函数适当地销毁,或者如果l由于足够早的catch条款而得以幸存,它仍将保留原始列表。

那是一个建设性的例子。对此问题眨眨眼,您还可以给出更具破坏性的示例,删除包含给定值(如果有)的第一个节点:

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(隐式)销毁该节点(当销毁由持有的旧值时)之前未链接(通过,它返回指针,但使原始null)p。当时只有一个节点被破坏。(在评论中提到的另一种形式中,此时间将留给std::unique_ptr实例的移动分配运算符的实现内部执行list;标准规定20.7.1.2.3; 2该运算符应“像调用reset(u.release())“,那么这里的时间也应该安全。)

请注意,prependremove_first不能由谁存储在本地客户端调用node变量始终非空列表,这是正确的,因为在给定的实现不能为这样的情况下工作。

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

当仅获取指针所有权时,这是首选的模式。我想通过支票来调用此方法:调用者必须通过签署支票来接受放弃所有权,就像提供现金一样,但是实际提款被推迟到被调用函数实际窃取指针之前(与使用模式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>&复制构造函数一样),因此可以使用in中的auto_ptr<T>左值来调用,之后将其重置为null。由于参数传递从模式2更改为3,因此现在必须将旧代码重写为旧代码,然后才能继续工作。我了解委员会在这里不喜欢模式2,但是他们可以通过定义pstd::shared_ptr<T> q(p)pstd::shared_ptr<T> q(std::move(p))std::shared_ptr<T>(auto_ptr<T> p)取而代之的是,他们可以确保旧代码无需修改就可以正常工作,因为(与唯一指针不同)自动指针可以无提示地引用到值(指针对象本身在过程中重置为null)。显然,委员会比模式1更偏爱提倡模式3,以至于他们选择主动破坏现有代码,而不是即使已经弃用的模式也使用模式1。

什么时候比模式1更喜欢模式3

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

  • 传递引用比创建临时指针和废除旧指针要有效得多(处理现金有些费力)。在某些情况下,在实际窃取指针之前,可能会将指针多次多次传递给另一个函数。这样的传递通常需要编写std::move(除非使用模式2),但是请注意,这只是一个强制转换,实际上不执行任何操作(特别是不进行取消引用),因此其成本为零。

  • 可以想象,在函数调用的开始与它(或某些包含的调用)的位置之间实际会指向对象的任何对象之间引发任何异常(并且该异常尚未在函数本身内部捕获) ),那么在使用模式1时,智能指针引用的对象将在catch子句可以处理异常之前被销毁(因为函数参数在堆栈展开时被破坏了),但在使用模式3时则不是。在这种情况下,调用方可以选择恢复对象的数据(通过捕获异常)。请注意,此处的模式1 不会导致内存泄漏,但是可能导致程序的数据无法恢复,这也是不希望的。

返回智能指针:始终按值

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


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

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

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

4

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


3

编辑:这个答案是错误的,即使严格来讲,代码也可以。我仅将其留在此处,因为其下的讨论太有用了。这另一个答案是我上次编辑此内容时给出的最佳答案:如何将unique_ptr参数传递给构造函数或函数?

基本思想::std::move是,传递您的人unique_ptr应该使用它来表达自己的知识,即他们知道unique_ptr传递的人将失去所有权。

这意味着您应该在方法中使用对a的右值引用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会因为没有显而易见的原因突然失去所有权而感到困惑。


2
命名的右值引用是左值。
R. Martinho Fernandes

你确定这是Base fred(::std::move(objptr));Base::UPtr fred(::std::move(objptr));
codablank1 2011年

1
补充一点:这段代码不会编译。您仍然需要std::move在构造函数和方法的实现中使用。即使您按值传递值,调用方仍必须使用它std::move来传递左值。主要区别在于,通过值传递该接口可以清楚地知道所有权将丢失。参见Nicol Bolas对另一个答案的评论。
R. Martinho Fernandes

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

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

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)

具有相同的身体。

而且......是什么evthandle()


3
有没有在不使用增益std::forward此处Base::UPtr&&总是一个右值引用类型,std::move并将其作为右值。已经正确转发了。
R. Martinho Fernandes

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

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

0

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

我了解传递右值引用可能会导致什么问题。但让我们将此问题分为两个方面:

  • 对于来电者:

我必须写代码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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.