每周贴士 #181: 访问StatusOr<T>里的值

(原文链接:https://abseil.io/tips/76 译者:clangpp@gmail.com)

每周贴士 #181: 访问StatusOr<T>里的值

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::Statusabsl::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->访问值的代码(不论是指针、StatusOroptional还是其他),必须先确认值存在。检验的代码要放在值被访问的地方附近,这样读者就能很容易地确认做了检验且代码没错。


  1. 根据value()函数的文档,如果开启了异常,它会抛出absl::BadStatusOrAccess异常(也许会被接住,也就是说程序不会终止)。如果编译时禁用了异常,代码会崩溃。尽管google3里默认不开启异常,StatusOr很快就会开源(开源世界里有些代码就会在编译时开启异常),而且对于跨平台代码,go/exception-safety的最佳实践也建议使用规制(constructs),因为其在开启和关闭异常的情况下都有合理的行为。 ↩︎

上一篇:[Android编程心得] Camera(OpenCV)自动对焦和触摸对焦的实现


下一篇:用postgreSQL做基于地理位置的app