天天看點

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

    異常(也許會被接住,也就是說程式不會終止)。如果編譯時禁用了異常,代碼會崩潰。 ↩︎

繼續閱讀