最近在學習 c++ 17 的一些新特性,為了加強記憶和理解,把這些內容作為筆記記錄下來,有理解不對的地方請指正,歡迎大家留言交流,
引言
在介紹之前,我們從一個問題出發,C++ 的函式如何回傳多個值?
比較有年代感的一種做法是將回傳值作為參考引數傳入,函式的回傳值用來標識運行狀態,比如像下面這樣
#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;
}
這種做法性能不錯,但可讀性會比較差,引數串列里既包含了入參也包含了出參,常見通過變數名前綴來標識,尤其是在出入參比較多的時候,后期維護會非常頭疼,
在 C++ 11 中新增了 tuple 這種資料結構的支持,自然也可以使用 tuple 來實作多個回傳值
#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 在這里用起來不是很爽的地方是需要刻意的記憶每個回傳值的位置,在回傳值數量比較多的時候就會帶來比較大的困擾,回傳值的語意表達的,
還有一種做法就是將函式回傳值定義成一個結構體,同時要回傳函式的運行狀態,我們可以考慮把這兩部分資料定義成一個 pair ,pair 可以理解為一種特殊的 tuple(只有 2 個元素的 tuple),
#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 很相似了,
std::optional
From cppreference -std::optional
The class templatestd::optionalmanages an optional contained value, i.e. a value that may or may not be present.
A common use case foroptionalis the return value of a function that may fail. As opposed to other approaches, such as std::pair<T,bool>,optionalhandles 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 帶來的好處:
- 省去了運行狀態的 bool 值的宣告,讓代碼更簡潔,更注重回傳值本身的語意
- 不用擔心額外的動態記憶體分配,這一點會在后面的文章里詳細展開
總結

鏈接:https://pan.baidu.com/s/1v5gm7n0L7TGyejCmQrMh2g 提取碼:x2p5
免費分享,但是X度限制嚴重,如若鏈接失效點擊鏈接或搜索加群 群號744933466,
通過對多回傳值的代碼不斷的重構,最后通過 std::optional 實作了一個比較滿意的版本,不過在這個程序中我們還遺漏了例外處理的部分,目前的實作方式在出例外時我們只知道沒有回傳值,但為什么出現例外卻無從得知,以及 std::optional 在記憶體和性能上的一些思考,還有 std::optional 其它場景下的應用介紹都放到下一篇文章里啦,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/84889.html
標籤:C++
上一篇:C++踩坑——用memset對vector進行初始化
下一篇:哈爾濱網路熱身賽
