c/c++语言开发共享C++17 新特性之 std::optional(上)

最近在学习 c++ 17 的一些新特性,为了加强记忆和理解,把这些内容作为笔记记录下来,有理解不对的地方请指正,欢迎大家留言交流。 引言 在介绍之前,我们从一个问题出发,C++ 的函数如何返回多个值? 比较有年代感的一种做法是将返回值作为引用参数传入,函数的返回值用来标识运行状态,比如像下面这样 # …

最近在学习 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> 这个数据结构实现的功能就跟c/c++开发分享C++17 新特性之 std::optional(上)要介绍 std::optional 很相似了。

std::optional

from cppreference -std::optional

the class template std::optional manages an optional contained value, i.e. a value that may or may not be present.
a common use case for optional 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 带来的好处:

  • 省去了运行状态的 bool 值的声明,让代码更简洁,更注重返回值本身的语意
  • 不用担心额外的动态内存分配,这一点会在后面的文章里详细展开

总结

C++17 新特性之 std::optional(上)

链接:https://pan.baidu.com/s/1v5gm7n0l7tgyejcmqrmh2g 提取码:x2p5

免费分享,但是x度限制严重,如若链接失效点击链接或搜索加群 群号744933466。

通过对多返回值的代码不断的重构,最后通过 std::optional 实现了一个比较满意的版本,不过在这个过程中我们还遗漏了异常处理的部分,目前的实现方式在出异常时我们只知道没有返回值,但为什么出现异常却无从得知,以及 std::optional 在内存和性能上的一些思考,还有 std::optional 其它场景下的应用介绍都放到下一篇文章里啦。

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/c-cdevelopment/600847.html

(0)
上一篇 2021年5月9日
下一篇 2021年5月9日

精彩推荐