前言
春節就是拋拋走,趁著搭車的時間看完 JeanHeyd Meneide (ThePhantomDerpstorm/ThePhD) 的血淚控訴 To Bind and Loose a Reference1,他聲稱:標準委員會因為一個不存在的對手,訂定了一個殘缺的 std::optional
。文章非常值得一讀,寫不出配得上的心得,只好寫一篇筆記。
Nullable 型別
C++17 增添了 std::optional<T>
,C++20 加入了 std::variant<Ts...>
,C++2x 預計會出現 std::expected<T>
,這些工具的相同點在於他們都是 nullable
型別 ─ 物件狀態可以是含有 T
或者不含有 T
(也就是 null),雖然應用在不同的情境上,但某種程度上都可以理解為特化版的指標。
Reference 有甚麼問題?
std::optional
在 cppreference.com 裡的描述2 裡面有一句話:
There are no optional references; a program is ill-formed if it instantiates an
optional
with a reference type.
同樣適用於 std::variant
、std::expected
。其實在 Boost.Optional 或是 Boost.Variant 都是支援 reference 型別的,然而 C++ 標準委員會對下面這段程式分裂成兩派不同的見解 (範例取自 JeanHeyd Meneide):
#include <optional>
#include <iostream>
int main (int, char*[]) {
int x = 5;
int y = 24;
std::optional<int&> opt = x;
opt = y;
std::cout << "x is " << x << std::endl;
std::cout << "y is " << y << std::endl;
return 0;
}
一派認為應該印出 5
與 24
,也就是 opt = y
這個動作會將 reference 重新指向 y (rebinds);而另一派則認為應該印出 24
與 24
,也就是該動作將 x 賦值為 y 的值 (assigns through) 。
JeanHeyd Meneide 是屬於 rebinds 派,他的文章內用了個例子說明 assign through 的問題,節錄於後
std::optional<int&> cache::retrieve (std::uint64_t key) {
std::optional<int&> opt = this->get_default_resource_maybe();
// blah blah work
auto found_a_thing = this->lookup_resource(key);
if (found_a_thing) {
int& resource =
this->get_specific_resource(found_a_thing);
// do stuff with resource, return
opt = resource;
}
return opt; // optional says if we got something!
}
當 found_a_thing
為 false
的時候,retrieve
回傳的值是一個 reference 到 default resource 的 optional,然而當 found_a_thing
為 true
的時候,在 assign through 的作用下 opt = resource
會將 default resource 覆寫成 get_specific_resource
回傳的值 (完整的說法是回傳的 reference 所指涉的值);也就是說每次呼叫 retrieve
,只要有找到東西,default resource 的值就會改變。
這種 bug 可以算是夢靨級了吧?
不存在的對手
在標準委員會的會議中, JeanHeyd Meneide 主張的 rebinds 版本沒被接受 (過程曲折,讓他灰心到 quit ,請讀他的文章),之後他做了一些調查發現:現存的、有足夠使用者數量的、業界採用的 optional 實作版本,幾乎都沒有採用 assign through ,也就是委員會期望的 ─ 正確的 assign through 實作 ─ 根本是 rebinds 不存在的對手。
留言
張貼留言