在C++11中如何将对象所有权从一个unique_ptr转移到另一个unique_ptr?

59

C++11 中我们可以使用 std::move() 将对象的所有权转移到另一个 unique_ptr 中。所有权转移后,放弃所有权的智能指针将变为 nullget() 返回 nullptr

std::unique_ptr<int> p1(new int(42));
std::unique_ptr<int> p2 = std::move(p1); // Transfer ownership

在将所有权转移给另一个unique_ptr的情况下,这将有什么用处?


4
没问题。标准库中的智能指针不应该被视为自动释放的指针,而是基于所有权的概念。如果您有一些只能被单个实体“拥有”的数据,请使用独占指针。 - Some programmer dude
4
当你处理无法复制的数据(如线程或套接字)并且需要将其从一个位置替换到另一个位置时,它非常有用(例如,将其放入向量中)。 - user2556165
2个回答

53
以下情况涉及将所有权从一个 unique_ptr 转移到另一个 unique_ptr:从函数返回,以及传递作为参数到像构造函数这样的函数中。
假设您有一些多态类型 Animal:
struct Animal {
  virtual ~Animal() {}
  virtual void speak() = 0;
};

具体的子类包括CatDog

struct Cat : Animal {
  void speak() override { std::cout << "Meow!\n"; }
};

struct Dog : Animal {
  void speak() override { std::cout << "Woof!\n"; }
};

你希望创建一个简单的工厂,根据顺从度的要求值创建宠物,并返回指针。我们希望宠物工厂将所创建的宠物的所有权转移给调用者,因此合理的返回类型是 std::unique_ptr<Animal>

std::unique_ptr<Animal> createPet(double obedience) {
  if (obedience > 5.0)
    return std::make_unique<Dog>();
  return std::make_unique<Cat>();
} 

现在,假设我们想要创建一个拥有宠物的 House,那么我们可能希望将宠物传递到 House 的构造函数中。关于如何最好地将 unique_ptr 传递给构造函数存在一些争议(请参见此博客文章的评论),但代码大致如下:

class House {
 private:
  std::unique_ptr<Animal> pet_;
 public:
  House(std::unique_ptr<Animal> pet) : pet_(std::move(pet)) {}
};

我们将 unique_ptr 传递给构造函数,然后将其"移动"到成员变量中。

调用代码可能如下所示:

  auto pet = createPet(6.0);
  House house(std::move(pet));

在构建完House之后,pet变量将会是nullptr,因为我们已经将宠物的所有权转移到了House

实时演示


1
将构造函数更改为采用对unique_ptr的r值引用是否更好?例如,House(std :: unique_ptr <Animal> && pet)。那么在pet_的初始化列表中就不需要std :: move()调用了吗? - jfritz42
4
@jfritz42 一个右值引用本身也是一个左值,所以你仍然需要使用 std::move试一试 - Chris Drew
好观点!顺便说一下,这似乎是一种新的C++惯用语。我想知道是否已经有人给它起了名字。"转移所有权惯用语"? - jfritz42
请注意,您还可以执行移动赋值:uptr1 = std::move(uptr2) - Shital Shah

8
例如,如果您调用一个函数,您可以在参数列表中移动您的unique_ptr,以便它成为函数签名的一部分。
foo ( std::unique_ptr<T>&& ptr )

你可以使用foo进行调用。
foo( std::move(myPtr) );

请注意,std::move 是一个无条件的转换操作,而 unique_ptr 是一个具有状态的对象,其中一部分状态是该 unique_ptr 管理的指针,使用 std::move 时,您正在转换整个对象,实际上并没有改变所有权的任何内容,使用 std::move 时,std::unique_ptr 没有任何特殊之处,因为 std::move 并不关心任何特定的内容,正如我所说,它是一个无条件的转换操作,只是简单地将 unique_ptr 强制转换为另一种类型。如果您想讨论指向您的 unique_ptr 的对象所有权的转移,则应考虑 std::unique_ptr<T> 提供的 swap

1
unique_ptr 在你将其 move 到另一个 unique_ptr 时,会转移所管理对象的所有权。 - Jonathan Potter
说实话,我不太确定你的意思。但是当你使用move将一个unique_ptr推入到一个vector中时,你认为会发生什么? - Jonathan Potter
2
@user1308004,虽然std::move实际上并没有移动对象,只是为了准备移动,但在大多数情况下,这种区别并不重要,因此他们决定称之为std::move。我不认为使用std::move将所有权从一个unique_ptr转移到另一个unique_ptr会有什么难以理解的地方。这就是unique_ptr具有移动构造函数和移动赋值运算符的原因。 - Chris Drew
2
如果你想在函数调用中使用“swap”来转移所有权,你必须通过非const引用传递,这样做并不表达意图。通过值传递或rvalue-ref更能表达意图。 - Chris Drew
1
我并不建议通过const引用传递。我建议通过值传递或rvalue-ref传递。 - Chris Drew
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接