(原文链接:https://abseil.io/tips/76 译者:clangpp@gmail.com)
每周贴士 #181: 访问StatusOr<T>里的值
- 最初发布于:2020-07-09
- 作者:Michael Sheely
- 更新于:2020-09-02
- 短链接:abseil.io/tips/181
StatusOr
的可读性(StatusOr<Readability>
):你不需要做选择!
当需要访问absl::StatusOr<T>
里的对象的时候,我们应该尽量让访问 安全,清晰,和 高效。
建议
如需访问StatusOr
里的值,应该先调用ok()
确认值存在,然后调用operator*
或operator->
。
// 用于unique_ptr的范式...
std::unique_ptr<Foo> foo = TryAllocateFoo();
if (foo != nullptr) {
foo->DoBar(); // 操作值对象
}
// ...或用于optional的范式...
absl::optional<Foo>; foo = MaybeFindFoo();
if (foo.has_value()) {
foo->DoBar();
}
// ...也是处理StatusOr的理想范式。
absl::StatusOr<Foo> foo = TryCreateFoo();
if (foo.ok()) {
foo->DoBar();
}
你可以限定StatusOr
的作用域,方法是将其定义在if
语句的初始化部分(译者注:if
后的小括号里,分号之前),然后在条件部分(译者注:if
后面的小括号里,分号之后)检查ok()
。如果StatusOr
是被立即使用的话,你通常应该以此方式限定其作用域(参考贴士 #165)。
if (absl::StatusOr<Foo> foo = TryCreateFoo(); foo.ok()) {
foo->DoBar();
}
StatusOr
的背景知识
absl::StatusOr<T>
类型是一个具有值语义(value semantics)(译者注:区别于引用语义)的标签联合(tagged union),表达如下情况之一:
- 一个
T
类型的值可用, - 一个
absl::Status
错误(!ok()
)表明为什么值不存在。
你可以在贴士 #76了解更多关于absl::Status
和absl::StatusOr
的信息。
安全性,清晰度,和效率
把StatusOr
对象当成智能指针,可以在保持安全和高效的前提下提升清晰度。下面,我们将考虑一些其他你可能见过的访问StatusOr
的方式,以及为什么我们推荐使用间接访问操作符(译者注:operator*
和operator->
)。
其他的值访问方式的安全性问题
absl::StatusOr<T>::value()
怎么样?
absl::StatusOr<Foo> foo = TryCreateFoo();
foo.value(); // 行为依赖于构建模式(build mode)。
在这里,其行为依赖于构建模式——具体来说,编译代码的时候开没开异常。1因此,读者不清楚一个失败状态(error status)是否会让程序终止。
value()
方法结合了两个动作:先测试有效性,再访问值。因此,只有 两个动作都需要执行的时候才应该被使用(而且即便如此,仍需三思,并记住其行为依赖于构建模式)。如果已知状态是OK
,那理想的访问器的语义应该是直接访问值,这恰好就是operator*
或operator->
的行为。除了让代码更精确地表达你的意图之外,其访问行为的效率最差也是和value()
的"先测试有效性,再访问值"一样好。
避免给同一个对象多个名字
把StatusOr
对象当成智能指针,还让我们避免两个变量指向同一个值的尴尬情况。进而避免了由此带来的命名困境(译者注:程序员起名困难症)和auto
过度使用。
// 不看TryCreateFoo()的话,读者没法立即猜出返回值类型(optional? pointer? StatusOr?)
auto maybe_foo = TryCreateFoo();
// ...更别说不用`.ok()`而是用隐式bool类型转换。
if (!maybe_foo) { /* 处理foo不存在的情况 */ }
// 现在两个变量(maybe_foo, foo)代表同一个值了。
Foo& foo = maybe_foo.value();
避免_or
后缀
在检查有效性之后使用使用StatusOr
变量的内在值类型(而不是为同一个值创建多个变量),还有另一个好处:我们可以为StatusOr
选择最好的变量名,而不需要(或倾向于)增加一个前缀或后缀。
// 类型已经说清了它是unique_ptr;`foo`就挺好。
std::unique_ptr<Foo> foo_ptr;
absl::StatusOr<Foo> foo_or = MaybeFoo();
if (foo_or.ok()) {
const Foo& foo = foo_or.value();
foo.DoBar();
}
如果只有一个变量,我们可以避免掉后缀,以内含的值来给变量命名命名(正如我们对指针做的那样)。
absl::StatusOr<Foo> foo = MaybeFoo();
if (foo.ok()) {
MakeUseOf(*foo);
foo->DoBar();
}
解决方案
先检查absl::StatusOr
对象的有效性(正如你对智能指针或optional
所做的),再以operator*
或operator->
访问之,这种方式易读,高效,而且安全。
它帮助你避免了前面提到的可能的命名歧义陷阱,而且不需要用到任何宏。
通过operator*
或operator->
访问值的代码(不论是指针、StatusOr
、optional
还是其他),必须先确认值存在。检验的代码要放在值被访问的地方附近,这样读者就能很容易地确认做了检验且代码没错。