为共享指针移动构造函数和 = 运算符

Move Constructor and = operator for Shared Pointer

说,我有一个 class:

class GameObject ///header file
{
    ....
    std::shared_ptr<Transform> transform;
}
///cpp file
//Copy Ctor
GameObject::GameObject(const GameObject& rhs)
   :transform(rhs.transform)
{}
//Move CTor
GameObject::GameObject(GameObject&& rhs)
    :transform(std::move(rhs.transform))
{}
  1. 为具有 shared_ptr 成员变量的 class 创建移动构造函数是否正确?或者我是否需要在移动后调用 rhs.transform.reset() 来取消分配 rhs?
  2. 复制构造函数怎么样?
  3. 据推测,复制和移动分配看起来与 ctors 基本相同,只是最后有一个 return *this

您的复制和移动构造函数等同于隐式构造函数。删除它们。您不需要显式写出它们,因为 std::shared_ptr 的复制和移动构造函数正确地实现了这两个操作。

Or do I need to to call rhs.transform.reset() to de-allocate the rhs after the move?

不,移动后的对象将失去所有权:

shared_ptr(shared_ptr&& r) noexcept;
template<class Y> shared_ptr(shared_ptr<Y>&& r) noexcept;

Remark: The second constructor shall not participate in overload resolution unless Y* is convertible to T*.

Effects: Move-constructs a shared_ptr instance from r. Postconditions: *this shall contain the old value of r. r shall be empty. r.get() == nullptr.

至于复制和移动赋值运算符,它们也是等价的。移动赋值将正确转移所有权,复制构造函数将执行浅拷贝,以便两个 shared_ptr 都拥有所有权。

如果您真的需要浅拷贝(共享所有权),那么 shared_ptr 是正确的工具。否则,如果您想实现 唯一所有权 .

,我建议您使用 unique_ptr
  1. 这段代码是正确的。如果 GameObject 是可移动的,那么移动 transform 就很有意义了。 shared_ptr 移动构造函数将为您做正确的事情 - 它将 转让 Transform 的所有权。您不需要调用 reset(),这是一个无关的操作 - 您应该只需要依赖您的成员对象的移动构造函数来正确实现,对于 shared_ptr 它们肯定是。

  2. 会员复制正确,shared_ptr会为您正确复制。

  3. 是的。

请注意,如果您的 class 完全由实现了所有正确运算符的对象组成,则您无需自己编写它们。默认构造函数和赋值运算符已经按成员 copy/move 执行,这是完全正确的。不必编写任何基本的 5 个函数称为 Rule of Zero:

class GameObject {
    std::shared_ptr<Transform> transform;
    std::shared_ptr<SomethingElse> foo;
};

GameObject obj = ...;
GameObject obj2 = obj;             // correct by default
GameObject obj3 = std::move(obj2); // correct by default
  1. 根据标准,shared_ptr 的移动构造函数将原始 shared_ptr 留空,因此不需要 reset() 调用。

  2. 您编写的复制构造函数将在多个 GameObject 之间共享一个 Transform 对象。也许这就是你想要的。如果没有,你需要像 transform(new Transform(*rhs.transform)).

  3. 这样的东西
  4. 本质上,但是您会使用赋值而不是构造成员(因为后者在句法上无效)。不过要小心代码重复。另一种方法可能是使用像复制和交换这样的习惯用法将工作委托给构造函数:

    GameObject &operator=(const GameObject &rhs) {
      GameObject tmp(rhs);
      tmp.swap(*this);
      return *this;
    }
    

此外,正如其他一些人已经指出的那样,如果您的构造函数和赋值与编译器生成的默认值相同(目前是这样),则删除它们并仅使用默认值会更短且更安全.