最近在学习 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 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 值的声明,让代码更简洁,更注重返回值本身的语意
  • 不用担心额外的动态内存分配,这一点会在后面的文章里详细展开

总结

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

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

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

C++17 新特性之 std::optional(上)的更多相关文章

  1. c++17 新特性

    编译环境说明:gcc 8.1 + eclipse +windows 10 eclipse cpp默认支持c++14,做c++17开发时,需要手动进行配置. 1.关键字 1)constexpr c++1 ...

  2. Java 17 新特性:switch的模式匹配(Preview)

    还记得Java 16中的instanceof增强吗? 通过下面这个例子再回忆一下: Map<String, Object> data = new HashMap<>(); da ...

  3. C++17新特性optional和string_view

    1. optional的作用 类模板 std::optional 管理一个可选的容纳值,即可以存在也可以不存在的值. 一种常见的 optional 使用情况是一个可能失败的函数的返回值.与其他手段,如 ...

  4. C++11 & C++14 & C++17新特性

    C++11:C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto.decltype,和模板的大量改进. 新的关键字 auto C++11中引入auto第一种作用是为了自动类型推导 ...

  5. 从Java 9 到 Java 17 新特性梳理

    Java 9 新的创建集合的方法  // [1, 2, 3, 4]  List<Integer> integers = List.of(1, 2, 3, 4);  // {1,2,3}   ...

  6. C++11新特性之 std::forward(完美转发)(转)

    我们也要时刻清醒,有时候右值会转为左值,左值会转为右值. (也许“转换”二字用的不是很准确) 如果我们要避免这种转换呢? 我们需要一种方法能按照参数原来的类型转发到另一个函数中,这才完美,我们称之为完 ...

  7. C++11新特性之 std::forward(完美转发)

    我们也要时刻清醒,有时候右值会转为左值,左值会转为右值. (也许“转换”二字用的不是很准确) 如果我们要避免这种转换呢? 我们需要一种方法能按照参数原来的类型转发到另一个函数中,这才完美,我们称之为完 ...

  8. C++ 11新特性:std::future & std::shared_future) (转载)

    上一讲<C++11 并发指南四(<future> 详解二 std::packaged_task 介绍)>主要介绍了 <future> 头文件中的 std::pack ...

  9. Android5.0新特性:RecyclerView实现上拉加载更多

    RecyclerView是Android5.0以后推出的新控件,相比于ListView可定制性更大,大有取代ListView之势.下面这篇博客主要来实现RecyclerView的上拉加载更多功能. 基 ...

随机推荐

  1. js中的原型,原型链和继承

    在传统的基于Class的语言如Java.C++中,继承的本质是扩展一个已有的Class,并生成新的Subclass. 由于这类语言严格区分类和实例,继承实际上是类型的扩展.但是,JavaScript最 ...

  2. 【转】面试题:实现一个队列,这个队列除了有EnQueue, DeQueue操作,还有一个Max操作,三个操作复杂度都是O(1)

    1.每次  新元素进栈的时候,栈里面的元素需要排序 2.让最小的或者最大的元素位于栈顶,这样就可以在O(1)时间内获得最小或者最大的值了, ------ 3.上面的想法  不能保证,进栈(进了队列)之 ...

  3. ETL中获取公共时间参数变量释义

  4. 景点API支持查询携程旅游门票景点详情

    门票景点详情,景点api支持查询携程旅游门票景点详情. 接口名称:景点api 接口平台:开放api 接口地址:http://api2.juheapi.com/xiecheng/senicspot/ti ...

  5. angular6 使用daterangepicker的注意事项

    具体使用方法可参考这篇博客:https://blog.csdn.net/qq_43225030/article/details/84973086 需要注意的地方是,在dateRangePicker函数 ...

  6. centos 7.3 安装 mysqldb 报错 EnvironmentError: mysql_config not found ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

    ERROR: Command errored : command: /usr/bin/python -c 'import sys, setuptools, tokenize; sys.argv[0] ...

  7. 【Linux】文件拷贝-Linux当前目录所有文件移动到上一级目录(转)

    Linux当前目录所有文件移动到上一级目录 mv * ../

  8. Python学习笔记8 pip换源

    换pip源 安装好 python 后,有一个问题就是 pip 安装的默认下载地址在国外.要换一下,不然蜗牛速度,怎么追赶世界?(瞎扯一下,活跃气氛~) Ubuntu下,进入终端(ctrl + alt ...

  9. 1.Java介绍

    第一章 走进Java 一.Java技术体系 Java技术体系组成部分:Java程序设计语言.Java虚拟机.Class文件格式.Java API类库 JDK:Java程序设计语言 + Java虚拟机 ...

  10. 洛谷 p1516 青蛙的约会 题解

    dalao们真是太强了,吊打我无名蒟蒻 我连题解都看不懂,在此篇题解中,我尽量用语言描述,不用公式推导(dalao喜欢看公式的话绕道,这篇题解留给像我一样弱的) 进入正题 如果不会扩展欧里几德的话请先 ...