本周小贴士#123: absl::optional和std::unique_ptr
Posted -飞鹤-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了本周小贴士#123: absl::optional和std::unique_ptr相关的知识,希望对你有一定的参考价值。
作为totW#123最初发表于2016年9月6日
由Alexey Sokolov (sokolov@google.com) and Etienne Dechamps (edechamps@google.com)创作
如何存储值?
此贴士讨论了几种存储值的方法。此处我们使用类成员变量作为示例,但是以下的许多点同样也适用于局部变量。
#include <memory>
#include "third_party/absl/types/optional.h"
#include ".../bar.h"
class Foo
...
private:
Bar val_;
absl::optional<Bar> opt_;
std::unique_ptr<Bar> ptr_;
;
作为一个纯对象
这是最简单的方法。val_分别在Foo的构造函数的开头和Foo析构函数的末尾被构造和销毁。如果Bar有一个默认的构造函数,那么它甚至不需要显式初始化。
val_使用起来非常安全,因为它的值不能为null。这消除了一类潜在的错误。
但是bare对象不是很灵活:
- val_的生命周期基本与它的父Foo对象的生命周期相关,这有时并不是想要的。如果Bar支持移动或交换操作,那么能够通过这些操作替换val_的内容,然而对val_的任何现有的指针或引用继续指向或引用相同的val_对象(作为容器),而不是存储在其中的值。
- 需要传递给Bar的构造函数的任何参数都需要在Foo的构造函数的初始化列表中进行计算,如果涉及复杂的表达式,这可能是困难的。
作为absl::optinal
纯对象的简单性和std::unqiue_ptr的灵活性。对象存储在Foo中,但与纯对象不同,absl::optional可以为空。它可以随时通过赋值(opt_=)或通过在适当位置构造对象来输入。
因为是内联存储的,所以在栈上分配大对象的常见警告同样适用,正如纯对象一样。另外请注意,空absl::optional使用的内存和输入的内存一样多。
与纯对象相比,absl::optional有一些缺点:
- 对于读者来说,对象的构造和析构的地方都不明显。
- 有访问不存在对象的风险。
作为std::unique_ptr
这是最灵活的方法。对象存储在Foo外。就像absl::optional一样,std::unique_ptr能够为空。然而,不像absl::optional,它可以将对象的所有权转移给其他对象(通过移动操作),从其他对象获取对象的所有权(通过构造函数或者赋值函数),或者假定对某个对象的原生指针的所有权(在构造或通过ptr_=absl::WrapUnique(…)),参见TotW126.
当std::unique_ptr为null时,它没有分配对象,只消耗指针1的大小。
如果对象可能需要超出std::unique_ptr的作用域(所有权转移),那么在std::unique_ptr中包装对象是必须的。
这种灵活性伴随着一些成本:
- 增加读者的认知负担:
- 不容易知道里面存储了什么(Bar,或从Bar派生的)。然而,它同样也会减少认知负担,因为读者能够只聚集于指针所持有的基本接口。
- 在对象构造或析构的地方,它甚至比absl::optional更不明显,因为对象的所有权可以转移。
- 与absl::optional一样,有访问不存在对象的风险——著名的空指针解引用。
- 指针引用了额外的间接层,那需要进行堆分配,并且对CPU缓存不友好;重要与否依赖于特定的用例。
- std::unique_ptr即便是不可复制的。这依然可以防止Foo可复制。
结论
与往常一样,努力避免不必要的复杂性,并使用最简单的东西。如果适用你的情况,优先纯对象。否则,尝试absl::optional.请使用std;:unique_ptr作为最后的方法。
Bar | absl::optional | std::unique_ptr | |
---|---|---|---|
支持延迟构造 | ✓ | ✓ | |
一直安全访问 | ✓ | ||
能够转移Bar的所有权 | ✓ | ||
能够存储Bar的子类 | ✓ | ||
可移动性 | 如果Bar可移动 | 如果Bar可移动 | ✓ |
可复制性 | 如果Bar可复制 | 如果Bar可复制 | |
CPU缓存友好 | ✓ | ✓ | |
无堆分配开销 | ✓ | ✓ | |
内存用法 | sizeof(Bar) | sizeof(Bar) + sizeof(bool) | 为空时sizeof(Bar*),否则sizeof(Bar*) + sizeof(Bar) |
对象生命周期 | 和封闭作用域相同 | 严格限定封闭作用域 | 不严格 |
调用f(Bar*) | f(&val) | f(&opt_.value())或f(&opt_) | f(ptr_.get()) 或f(&*ptr_) |
移除值 | N/A | opt_.reset(); 或 opt_ = absl::nullopt; | ptr_.reset(); 或 ptr_ = nullptr; |
- 在非空自定义删除器的情况下,该删除器还有一个额外的空间。 ↩
- 也可以添加填充。
以上是关于本周小贴士#123: absl::optional和std::unique_ptr的主要内容,如果未能解决你的问题,请参考以下文章