[译]C++17,optional, any, 和 variant 的更多细节

2023-10-23 12:20
文章标签 c++ 细节 17 optional variant

本文主要是介绍[译]C++17,optional, any, 和 variant 的更多细节,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

看到一个介绍 C++17 的系列博文(原文),有十来篇的样子,觉得挺好,看看有时间能不能都简单翻译一下,这是第六篇~

std::optional, std::any, 和 std::variant 有一个共同特点:他们都支持就地构造.另外的,std::variant 还支持访问者模式.

首先,我们要了解一下这3种数据类型的功能作用.

  • std::optional 是一种可能包含也可能不包含某一类型对象的类型.
  • std::variant 是一种类型安全的联合体
  • std::any 是一种可以包含任意类型(指可复制类型)对象的类型

我在之前的文章中讲解了这3个数据类型的一些细节,不了解的朋友可以先去看看,相关内容这里就不再赘述了.

图

Construct in-place

什么是就地构造呢?以 std::optional<std::string> 为例来说明就是: 所谓就地构造,就是你可以直接使用 std::string 的构造参数来构造 std::optional<std::string>.

下面是一个简短的示例.

#include <optional>
#include <iostream>
#include <string>int main() 
{std::cout << std::endl;// C string literalstd::optional<std::string> opt1(std::in_place, "C++17");                        // 1// 5 characters 'C'std::optional<std::string> opt2(std::in_place, 5, 'C');                          // 2// initializer liststd::optional<std::string> opt3(std::in_place, { 'C', '+', '+', '1', '7' });      // 3// Copy constructorstd::optional<std::string> opt4(opt3);                                          // 4std::cout << *opt1 << std::endl;std::cout << *opt2 << std::endl;std::cout << *opt3 << std::endl;std::cout << *opt4 << std::endl;std::cout << std::endl;return 0;
}

代码中的 opt1(第10行), op2(第13行) 和 op3(第16行) 都使用了 std::in_place 标记来进行构造,这意味着 std::optional 的构造参数将直接用于调用 std::string 的构造函数.所以在上述代码中, opt1 中 std::string 的构造函数参数即为 C 风格字符串(“C++17”), op2 中是5个单字符’C’, op3 中则是初始化列表({ ‘C’, ‘+’, ‘+’, ‘1’, ‘7’ }).另外,代码中的 opt4(第19行)并未使用就地构造方法,而是调用了 std::optional 的复制构造函数(复制了op3).

程序的输出如下:

图

上述的就地构造是不是觉得有些熟悉?其实早在 C++11 中,标准库容器就引入很多用于增加容器元素的接口方法,这些方法都以 emplace 开头,功能上就是提供了就地构造的方法.以 std::vector<int> vec 为例,借助其支持的 emplace_back 方法,我们可以直接调用 vec.emplace_back(5) 来增加 vec 的末尾元素,这等同于下面代码: vec.push_back(int(5)).

std::variant 还支持 std::visit 方法(即精典的设计模式:访问者).

Visit a list of variants

std::visit 方法允许你对一个 std::variants 列表应用访问者模式,而相应的访问者必须是一个callable类型,所谓 callable 类型,是一种可以被调用的类型,通常是一个函数,一个函数对象或者一个 lambda 函数.简单起见,这里我仅使用 lambda 函数来举例说明.

#include <iostream>
#include <vector>
#include <typeinfo>
#include <type_traits>
#include <variant>int main()
{std::cout << std::endl;std::vector<std::variant<char, long, float, int, double, long long>>      // 1vecVariant = { 5, '2', 5.4, 100ll, 2011l, 3.5f, 2017 };// display each value                                                             for (auto& v : vecVariant) {std::visit([](auto&& arg) {std::cout << arg << " "; }, v);                // 2}std::cout << std::endl;// display each typefor (auto& v : vecVariant) {std::visit([](auto&& arg) {std::cout << typeid(arg).name() << " "; }, v); // 3}std::cout << std::endl;// get the sumstd::common_type<char, long, float, int, double, long long>::type res{};  // 4std::cout << "typeid(res).name(): " << typeid(res).name() << std::endl;for (auto& v : vecVariant) {std::visit([&res](auto&& arg) {res += arg; }, v);                          // 5}std::cout << "res: " << res << std::endl;// double each valuefor (auto& v : vecVariant) {std::visit([](auto&& arg) {arg *= 2; }, v);                           // 6std::visit([](auto&& arg) {std::cout << arg << " "; }, v);}std::cout << std::endl;return 0;
}

代码中我创建了 std::variants 的列表(代码第11行).每个 variant 都可以包含以下的任一类型:char, long, float, int, double, long long.遍历 variant 列表并对每一个 variant 应用 lambda 函数非常简单(代码第15行到17行).借助 typeid 函数,我便可以获得 variant 的实际类型(代码第22行到24行).到这里,我想你应该已经看出了代码中的访问者模式, std::vector<std::variant> 就是我应用各种函数(即访问者)的被访问数据结构.

现在,我想将各个 variant 的元素求和.求和之前,我需要在编译期确定所求和的结果类型,为此我使用了 std::common_type (代码第29行), std::common_type 可以给出 char, long, float, int, double, 和 long long 都可以进行隐式转换的类型(double类型).代码中的 res{} 定义将 res(求和结果) 初始化为了 0.0,并在第33行到35行执行了真正的求和操作.我甚至使用访问者动态的修改了 variant 中的元素(代码第40行).

程序的输出如下.Visual C++ 中的运行时类型信息(std::type_info)给出了非常易读的类型名称.

图

这篇关于[译]C++17,optional, any, 和 variant 的更多细节的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/267788

相关文章

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

C++的模板(八):子系统

平常所见的大部分模板代码,模板所传的参数类型,到了模板里面,或实例化为对象,或嵌入模板内部结构中,或在模板内又派生了子类。不管怎样,最终他们在模板内,直接或间接,都实例化成对象了。 但这不是唯一的用法。试想一下。如果在模板内限制调用参数类型的构造函数会发生什么?参数类的对象在模板内无法构造。他们只能从模板的成员函数传入。模板不保存这些对象或者只保存他们的指针。因为构造函数被分离,这些指针在模板外

C++工程编译链接错误汇总VisualStudio

目录 一些小的知识点 make工具 可以使用windows下的事件查看器崩溃的地方 dumpbin工具查看dll是32位还是64位的 _MSC_VER .cc 和.cpp 【VC++目录中的包含目录】 vs 【C/C++常规中的附加包含目录】——头文件所在目录如何怎么添加,添加了以后搜索头文件就会到这些个路径下搜索了 include<> 和 include"" WinMain 和

C/C++的编译和链接过程

目录 从源文件生成可执行文件(书中第2章) 1.Preprocessing预处理——预处理器cpp 2.Compilation编译——编译器cll ps:vs中优化选项设置 3.Assembly汇编——汇编器as ps:vs中汇编输出文件设置 4.Linking链接——链接器ld 符号 模块,库 链接过程——链接器 链接过程 1.简单链接的例子 2.链接过程 3.地址和

C++必修:模版的入门到实践

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C++学习 贝蒂的主页:Betty’s blog 1. 泛型编程 首先让我们来思考一个问题,如何实现一个交换函数? void swap(int& x, int& y){int tmp = x;x = y;y = tmp;} 相信大家很快就能写出上面这段代码,但是如果要求这个交换函数支持字符型

17.用300行代码手写初体验Spring V1.0版本

1.1.课程目标 1、了解看源码最有效的方式,先猜测后验证,不要一开始就去调试代码。 2、浓缩就是精华,用 300行最简洁的代码 提炼Spring的基本设计思想。 3、掌握Spring框架的基本脉络。 1.2.内容定位 1、 具有1年以上的SpringMVC使用经验。 2、 希望深入了解Spring源码的人群,对 Spring有一个整体的宏观感受。 3、 全程手写实现SpringM

C++入门01

1、.h和.cpp 源文件 (.cpp)源文件是C++程序的实际实现代码文件,其中包含了具体的函数和类的定义、实现以及其他相关的代码。主要特点如下:实现代码: 源文件中包含了函数、类的具体实现代码,用于实现程序的功能。编译单元: 源文件通常是一个编译单元,即单独编译的基本单位。每个源文件都会经过编译器的处理,生成对应的目标文件。包含头文件: 源文件可以通过#include指令引入头文件,以使

C++面试八股文:std::deque用过吗?

100编程书屋_孔夫子旧书网 某日二师兄参加XXX科技公司的C++工程师开发岗位第26面: 面试官:deque用过吗? 二师兄:说实话,很少用,基本没用过。 面试官:为什么? 二师兄:因为使用它的场景很少,大部分需要性能、且需要自动扩容的时候使用vector,需要随机插入和删除的时候可以使用list。 面试官:那你知道STL中的stack是如何实现的吗? 二师兄:默认情况下,stack使

剑指offer(C++)--孩子们的游戏(圆圈中最后剩下的数)

题目 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去