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

当前回答

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

(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-value引用,所以c++会自动为我们构造一个临时变量。它从我们通过std::move(nextBase)给函数的Base&&中创建了一个std::unique_ptr<Base>。这个临时函数的构造实际上是将值从nextBase移到函数参数n中。

(B)通过非const l-value引用

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

这必须在一个实际的l值(一个命名变量)上调用。它不能像这样调用temporary:

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引用

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

我没有展示一个实现,因为你不能从一个const&。通过传递一个参数&,你是说这个函数可以通过指针访问Base,但是它不能存储在任何地方。它不能宣称拥有它。

这很有用。不一定针对你的特定情况,但是能够给别人一个指针,并且知道他们不能(在不违反c++规则的情况下,比如不能放弃const)声明它的所有权总是好的。他们不能储存它。他们可以把它传给其他人,但其他人必须遵守同样的规则。

(D)通过r值引用

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

这或多或少与“通过非const l-value引用”的情况相同。区别在于两点。

你可以通过一个临时: Base newBase(std::unique_ptr<Base>(new Base));/ /法律现在. . 当传递非临时参数时,必须使用std::move。

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

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

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

问题是,事实并非如此。它不保证已经从。它可能已经从,但您只能通过查看源代码才能知道。你不能只从函数签名来判断。

建议

(A) By Value: If you mean for a function to claim ownership of a unique_ptr, take it by value. (C) By const l-value reference: If you mean for a function to simply use the unique_ptr for the duration of that function's execution, take it by const&. Alternatively, pass a & or const& to the actual type pointed to, rather than using a unique_ptr. (D) By r-value reference: If a function may or may not claim ownership (depending on internal code paths), then take it by &&. But I strongly advise against doing this whenever possible.

如何操作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值。这里仅用于演示目的)。

临时只是一个对oldPtr的r值引用。移动发生在newPtr的构造函数中。Unique_ptr的移动构造函数(一个对自身使用&&的构造函数)是实际移动的构造函数。

如果你有一个unique_ptr值,你想把它存储在某个地方,你必须使用std::move来进行存储。

其他回答

编辑:这个答案是错误的,尽管严格来说,代码是可以工作的。我把它留在这里只是因为下面的讨论太有用了。另一个答案是我上次编辑这篇文章时给出的最佳答案:如何将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突然失去了所有权。

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

我理解传递右值引用可能导致的问题。但是让我们把这个问题一分为二:

调用者:

我必须写代码基准newBase(std::move(<左值>))或基准newBase(<右值>)。

被:

标准库作者应该保证,如果它想拥有所有权,它会实际移动unique_ptr成员来初始化。

这是所有。

如果传递右值引用,它将只调用一个“move”指令,但如果传递值,它将调用两个。

是的,如果库作者不是这方面的专家,他可能不会移动unique_ptr来初始化成员,但这是作者的问题,而不是你。无论它是通过值还是右值引用传递,你的代码都是一样的!

如果您正在编写一个库,现在您知道应该保证它,那么就这样做吧,传递右值引用比传递值是更好的选择。客户端谁使用你的库将只是编写相同的代码。

现在,回答你的问题。如何将unique_ptr参数传递给构造函数或函数?

你知道什么是最好的选择。

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

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

(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-value引用,所以c++会自动为我们构造一个临时变量。它从我们通过std::move(nextBase)给函数的Base&&中创建了一个std::unique_ptr<Base>。这个临时函数的构造实际上是将值从nextBase移到函数参数n中。

(B)通过非const l-value引用

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

这必须在一个实际的l值(一个命名变量)上调用。它不能像这样调用temporary:

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引用

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

我没有展示一个实现,因为你不能从一个const&。通过传递一个参数&,你是说这个函数可以通过指针访问Base,但是它不能存储在任何地方。它不能宣称拥有它。

这很有用。不一定针对你的特定情况,但是能够给别人一个指针,并且知道他们不能(在不违反c++规则的情况下,比如不能放弃const)声明它的所有权总是好的。他们不能储存它。他们可以把它传给其他人,但其他人必须遵守同样的规则。

(D)通过r值引用

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

这或多或少与“通过非const l-value引用”的情况相同。区别在于两点。

你可以通过一个临时: Base newBase(std::unique_ptr<Base>(new Base));/ /法律现在. . 当传递非临时参数时,必须使用std::move。

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

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

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

问题是,事实并非如此。它不保证已经从。它可能已经从,但您只能通过查看源代码才能知道。你不能只从函数签名来判断。

建议

(A) By Value: If you mean for a function to claim ownership of a unique_ptr, take it by value. (C) By const l-value reference: If you mean for a function to simply use the unique_ptr for the duration of that function's execution, take it by const&. Alternatively, pass a & or const& to the actual type pointed to, rather than using a unique_ptr. (D) By r-value reference: If a function may or may not claim ownership (depending on internal code paths), then take it by &&. But I strongly advise against doing this whenever possible.

如何操作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值。这里仅用于演示目的)。

临时只是一个对oldPtr的r值引用。移动发生在newPtr的构造函数中。Unique_ptr的移动构造函数(一个对自身使用&&的构造函数)是实际移动的构造函数。

如果你有一个unique_ptr值,你想把它存储在某个地方,你必须使用std::move来进行存储。

Base(Base::UPtr n):next(std::move(n)) {}

应该会好很多吧

Base(Base::UPtr&& n):next(std::forward<Base::UPtr>(n)) {}

and

void setNext(Base::UPtr n)

应该是

void setNext(Base::UPtr&& n)

同样的身体。

和…handle()中的evt是什么??

是的,如果你在构造函数中按值获取unique_ptr,你就必须这样做。明确是件好事。由于unique_ptr是不可复制的(私有复制ctor),您所写的应该会给您一个编译器错误。