天天看点

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

(原文链接:https://abseil.io/tips/181 译者:[email protected])

每周贴士 #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

还是其他),必须先确认值存在。检验的代码要放在值被访问的地方附近,这样读者就能很容易地确认做了检验且代码没错。

  1. 根据

    value()

    函数的文档,如果开启了异常,它会抛出

    absl::BadStatusOrAccess

    异常(也许会被接住,也就是说程序不会终止)。如果编译时禁用了异常,代码会崩溃。 ↩︎

继续阅读