我是否总是必须使用 unique_ptr 来表达所有权?
Do I always have to use a unique_ptr to express ownership?
假设我有一个 class A 拥有 class B 的对象。
- A负责创建和删除B的这个对象
- 不得将所有权转让给另一个 class。
- 创建 A 的对象后,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>
.
假设我有一个 class A 拥有 class B 的对象。
- A负责创建和删除B的这个对象
- 不得将所有权转让给另一个 class。
- 创建 A 的对象后,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>
.