最近在學習 c++ 17 的一些新特性,爲了增強記憶和理解,把這些內容做爲筆記記錄下來,有理解不對的地方請指正,歡迎你們留言交流。ios
在介紹以前,咱們從一個問題出發,C++ 的函數如何返回多個值?c++
比較有年代感的一種作法是將返回值做爲引用參數傳入,函數的返回值用來標識運行狀態,好比像下面這樣git
#include <iostream> using namespace std; int func(const string& in, string& out1, string& out2) { if (in.size() == 0) return 0; out1 = "hello"; out2 = "world"; return 1; } int main() { string out1, out2; int status = func("hi", out1, out2); if (status) { cout << out1 << endl; cout << out2 << endl; } return 0; }
這種作法性能不錯,但可讀性會比較差,參數列表裏既包含了入參也包含了出參,常見經過變量名前綴來標識,尤爲是在出入參比較多的時候,後期維護會很是頭疼。github
在 C++ 11 中新增了 tuple 這種數據結構的支持,天然也可使用 tuple 來實現多個返回值express
#include <iostream> #include <tuple> using namespace std; tuple<bool, string, string> func(const string& in) { if (in.size() == 0) return make_tuple(false, "", ""); return make_tuple(true, "hello", "world"); } int main() { if (auto [status, out1, out2] = func("hi"); status) { cout << out1 << endl; cout << out2 << endl; } return 0; }
上面這段代碼中的 `auto [status, out1, out2] = func("hi");` 是 C++ 17 中叫 Structured Bindings 的新特性,效果就是將多個返回值按照順序綁定到方括號中的變量名中。數據結構
tuple 在這裏用起來不是很爽的地方是須要刻意的記憶每一個返回值的位置,在返回值數量比較多的時候就會帶來比較大的困擾,返回值的語意表達的。app
還有一種作法就是將函數返回值定義成一個結構體,同時要返回函數的運行狀態,咱們能夠考慮把這兩部分數據定義成一個 pair ,pair 能夠理解爲一種特殊的 tuple(只有 2 個元素的 tuple)。ide
#include <iostream> using namespace std; struct Out { string out1 { "" }; string out2 { "" }; }; pair<bool, Out> func(const string& in) { Out o; if (in.size() == 0) return { false, o }; o.out1 = "hello"; o.out2 = "world"; return { true, o }; } int main() { if (auto [status, o] = func("hi"); status) { cout << o.out1 << endl; cout << o.out2 << endl; } return 0; }
目前這種作法能夠作到讓返回值更富有語意,而且能夠很方便的擴展,若是要增長一個新的返回值,只須要擴展示有的結構體就能夠了。正如上文所說,在 CppCoreGuidelines 中對於多返回值更建議使用 tuple 或 struct ,這樣作能讓返回值的語意更加明確。函數
最後這種作法中的 pair<bool, Out> 這個數據結構實現的功能就跟本文要介紹 std::optional 很類似了。性能
From cppreference -std::optional
The class templatestd::optional
manages an optional contained value, i.e. a value that may or may not be present.
A common use case foroptional
is the return value of a function that may fail. As opposed to other approaches, such as std::pair<T,bool>,optional
handles expensive-to-construct objects well and is more readable, as the intent is expressed explicitly.
類模板std::optional
管理一個 可選的容納值,便可以存在也能夠不存在的值。
一種常見的optional
使用狀況是一個可能失敗的函數的返回值。與其餘手段,如 std::pair<T,bool> 相比,optional
良好地處理構造開銷高昂的對象,並更加可讀,由於它顯式表達意圖。
std::optional 是在 C++ 17 中引入到標準庫中的,C++ 17 以前的版本能夠經過 boost::optional 實現幾乎相同的功能。
咱們來看一下使用 std::optional 來實現上面那段代碼的樣子
#include <iostream> #include <optional> using namespace std; struct Out { string out1 { "" }; string out2 { "" }; }; optional<Out> func(const string& in) { Out o; if (in.size() == 0) return nullopt; o.out1 = "hello"; o.out2 = "world"; return { o }; } int main() { if (auto ret = func("hi"); ret.has_value()) { cout << ret->out1 << endl; cout << ret->out2 << endl; } return 0; }
這段代碼中咱們看到了部分 std::optional 的用法,std::nullopt 是 C++ 17 中提供的沒有值的 optional 的表達形式,等同於 { } 。
建立一個 optional 的方法:
// 空 optiolal optional<int> oEmpty; optional<float> oFloat = nullopt; optional<int> oInt(10); optional oIntDeduced(10); // type deduction // make_optional auto oDouble = std::make_optional(3.0); auto oComplex = make_optional<complex<double>>(3.0, 4.0); // in_place optional<complex<double>> o7{in_place, 3.0, 4.0}; // initializer list optional<vector<int>> oVec(in_place, {1, 2, 3}); // 拷貝賦值 auto oIntCopy = oInt;
訪問 optional 對象中數據的方法:
// 跟迭代器的使用相似,訪問沒有 value 的 optional 的行爲是未定義的 cout << (*ret).out1 << endl; cout << ret->out1 << endl; // 當沒有 value 時調用該方法將 throws std::bad_optional_access 異常 cout << ret.value().out1 << endl; // 當沒有 value 調用該方法時將使用傳入的默認值 Out defaultVal; cout << ret.value_or(defaultVal).out1 << endl;
使用 std::optional 帶來的好處:
連接:https://pan.baidu.com/s/1v5gm7n0L7TGyejCmQrMh2g 提取碼:x2p5
免費分享,可是X度限制嚴重,如若連接失效點擊連接或搜索加羣 羣號744933466。
經過對多返回值的代碼不斷的重構,最後經過 std::optional 實現了一個比較滿意的版本,不過在這個過程當中咱們還遺漏了異常處理的部分,目前的實現方式在出異常時咱們只知道沒有返回值,但爲何出現異常卻無從得知,以及 std::optional 在內存和性能上的一些思考,還有 std::optional 其它場景下的應用介紹都放到下一篇文章裏啦。