最近在学习 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 其它场景下的应用介绍都放到下一篇文章里啦。