Tip of the Week 123 absl::optional和std::unique_ptr
如何存储值?
此贴士讨论了集中存储值得方法。此处我们使用类成员变量作为示例,但是以下的许多同样也适用于局部变量。
1  | 
  | 
作为一个纯对象
这是最简单的方法,val_分别在Foo的构造函数的开头和Foo析构函数的末尾被构造和销毁。如果Bar有一个默认的构造函数,那么它甚至不需要显式初始化。
val_使用起来非常安全,因为它的值不能为null。这小出了一类潜在的错误。
但是bar对象不是很灵活:
val_的生命周期基本与它的父Foo对象的生命周期相关,这有时并不是想要的。如果Bar支持移动或交换操作,那么能够通过这些操作替换val_的内容,然而对val_的任何现有的指针或引用继续指向或引用相同的val_对象(作为容器),而不是存储在其中的值。- 需要传递给
Bar的构造函数的任何参数都需要在Foo的构造函数的初始化列表中进行计算,如果涉及复杂的表达式,这可能是困难的。 
作为absl::optional
纯对象的简单性和std::unique_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作为最后的方法。










