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



假设有一个类a,它拥有一个类b的对象

  • A负责创建和删除b的对象。
  • 所有权不能转移到其他类。
  • 在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的公共接口的用户可以使用B*B&,这也是有区别的。但在这两种情况下,我确信所有权在我需要它时保持在A内。

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

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

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

std::unique_ptr<B> b表达了对std::unique_ptr<B>所管理的对象的唯一但可转让的所有权。

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

当然,如果a的公共接口的用户可以使用B*或B&但在这两种情况下,我确信所有权在我需要它时保持在A内。

无论成员是B b还是std::unique_ptr<B> b(也可以是std::optional<B>,std::vector<B>,…std::variant<B,C>),都可以创建返回B*的成员函数

有趣的是这段代码:

变体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;
};

对于变体2在这种情况下,调用A的另一个成员函数可能会使指针失效(如果该函数将另一个对象赋值给unique_ptr)。而对于变量1返回的指针(或引用)的有效性由A

实例的生存期决定。无论如何,您必须将B *视为非拥有的原始指针,并在文档中明确该原始指针的有效期。


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

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

您错过了unique_ptr的要点。它的创建正是为了允许通过简单的分配来转移所有权。如果所有权从未被转移过,那么通过使用containment来拥有子对象会简单得多。

内存分配在c++中并不重要,至少对于使用对象的程序员来说是这样,因为许多对象在内部使用动态分配,而不管对象的持续时间是多少。例如:std::string。在大多数实现中,自动std::string对象仍然会为其底层字符数组使用动态内存。


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

相关内容

  • 没有找到相关文章

最新更新