(原文链接:https://abseil.io/tips/181 译者:[email protected])
每周贴士 #181: 访问 StatusOr\<T\>
里的值
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
的背景知识
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
后缀
_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
还是其他),必须先确认值存在。检验的代码要放在值被访问的地方附近,这样读者就能很容易地确认做了检验且代码没错。
- 根据
函数的文档,如果开启了异常,它会抛出value()
异常(也许会被接住,也就是说程序不会终止)。如果编译时禁用了异常,代码会崩溃。 ↩︎absl::BadStatusOrAccess