我是否总是必须使用 unique_ptr 来表达所有权?

Do I always have to use a unique_ptr to express ownership?

假设我有一个 class A 拥有 class B 的对象。

通常,据我所知,在现代 C++ 中,我们会使用 unique_ptr 来表示 A 是此对象/引用的所有者:

// variant 1 (unique pointer)
class A {
  public:
    A(int param) : b(std::make_unique<B>(param)) {}

    // give out a raw pointer, so that others can access and change the object 
    // (without transferring ownership)
    B* getB() {
      return b.get();
    }
  private:
    std::unique_ptr<B> b;
};

有人建议我也可以改用这个方法:

// variant 2 (no pointer)
class A {
  public:
    A(int param) : b(B(param)) {}

    // give out a reference, so that others can access and change the object 
    // (without transferring ownership)
    B& getB() {
      return b;
    }
  private:
    B b;
};

据我了解,主要区别在于,在变体 2 中,内存是连贯分配的,而在变体 1 中,B 对象的内存可以分配到任何地方,必须先解析指针才能找到它。 当然,如果 A 的 public 界面的用户可以使用 B*B&,这也会有所不同。 但在这两种情况下,我都确信所有权在我需要时保留在 A 内。

我是否总是必须使用变体 1 (unique_ptrs) 来表达所有权? 使用变体 2 而不是变体 1 的原因是什么?

您错过了 unique_ptr 的要点。它的创建正是为了允许通过简单的分配进行所有权转移。如果永远不会转移所有权,则使用包含来拥有子对象要简单得多。

内存分配在 C++ 中并不重要,至少对于程序员 使用 对象而言,因为许多对象在内部使用动态分配,无论对象的持续时间如何。一个例子是 std::string。在大多数实现中,自动 std::string 对象仍会为其底层字符数组使用动态内存。


当然,库实现者应该关心他们的对象使用动态内存的方式。小字符串优化是在标准库的最近实现中发明的,以避免对小字符串进行动态分配(感谢 NathanOliver 的评论)。

所有权可以用不同的方式表达。

您的 B b 变体 2 是最简单的所有权形式。 class A 的实例独占存储在 b 中的对象,所有权不能转移给另一个对象或(成员)变量。

std::unique_ptr<B> b 表示对 std::unique_ptr<B>.

管理的对象的唯一但可转让的所有权

而且 std::optional<B>std::vector<B>、……或 std::variant<B,C> 表示所有权。

Also of course, it makes a difference if users of A's public interface can work with a B* or a B&. But in both cases, I am sure that ownership stays within A as I need it.

您总是可以创建一个 returns B* 的成员函数,无论该成员是 B b 还是 std::unique_ptr<B> b(或者 std::optional<B> , std::vector<B>, … std::variant<B,C>)

有趣的是这段代码:

变体 1

class A {
  public:
    A(int param) : b(B(param)) {}

    // give out a reference, so that others can access and change the object 
    B& getBRef() {
      return b;
    }


    B* getB() {
      return &b;
    }
  private:
    B b;
};

那么问题就少了:

变体 2

class A {
  public:
    A(int param) : b(std::make_unique<B>(param)) {}

    // give out a raw pointer, so that others can access and change the object 
    // (without transferring ownership)
    B* getB() {
      return b.get();
    }
  private:
    std::unique_ptr<B> b;
};

对于 Variant 2 情况,调用 A 的另一个成员函数可能会使指针无效(如果该函数例如将另一个对象分配给 unique_ptr).而对于 Variant 1,返回指针(或引用)的有效性由 A

实例的生命周期给出

在任何情况下,您都必须将 B * 视为非拥有原始指针,并在文档中明确说明该原始指针的有效期。


您选择哪种所有权取决于实际用例。 大多数时候,您会尝试保持最简单的所有权 B b。如果该对象应该是可选的,您将使用 std::optional<B>.

如果实施需要,例如如果您计划使用 PImpl,如果数据结构可能会阻止使用 B b(例如在树状或图形状结构的情况下),或者如果所有权必须可转让,您可能需要使用 std::unique_ptr<B>.