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

时间:2011-11-13 19:58:49

标签: c++ arguments c++11 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?

5 个答案:

答案 0 :(得分:759)

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

(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)非const 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-value reference

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

  3. 后者确实是问题所在。如果你看到这一行:

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

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

    问题在于它没有。它不是保证可能已被移除,但您只能通过查看源代码来了解。你无法从功能签名中分辨出来。

    推荐

    • (A)按价值:如果您要求某个功能声明unique_ptr所有权,请按值进行。
    • (C)通过const l-value引用:如果你的意思是函数在该函数的执行期间简单地使用unique_ptr,请按{{1} }。或者,将const&&传递给指向的实际类型,而不是使用const&
    • (D)按r值引用:如果某个功能可能会或可能不会声明所有权(取决于内部代码路径),请按unique_ptr进行操作。但我强烈建议尽可能不这样做。

    如何操纵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)

tl; dr:请勿使用unique_ptr

我相信您正在惹得一团糟-对于那些需要阅读,维护您的代码的人,也许还有那些需要使用它的人。

  1. 如果您有公开的unique_ptr成员,则仅获取unique_ptr构造函数参数。

unique_ptr包装用于所有权和生命周期管理的原始指针。它们非常适合本地使用-不好,实际上也不打算用于接口。要界面吗?将新课程记录为拥有所有权,并让其获取原始资源;或者,对于指针,可以按照{{3}}中的建议使用owner<T*>

仅当您的类的目的是持有unique_ptr的类,并且让其他人照原样使用这些unique_ptr时-才是您的构造函数或方法采用它们的合理性。

  1. 不要暴露您在内部使用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()是什么