C++ std::variant 总结

C++ std::variant 总结

文章目录

        • 一、std::variant 的由来
        • 二、std::variant 用法简介
          • 2.1、类型安全保证
          • 2.2、访问 std::variant
        • 三、std::variant vs OO

 本文来记录一下对C++标准库的 std::variant (带标签的联合体)用法的总结。参考文章: hhttps://boolan.com/

一、std::variant 的由来

  根据 cppreference 官网的概念, std::variant 是类型安全的共用体。 C语言里面共用体是把不同类型的数据成员存储在同一块内存区域上, 它与结构体不一样, 结构体的每个成员都存储在独立的地址空间内, 所以对任何成员的写操作都不改变其他成员的值, 访问的时候也是基于基址偏移的方式进行读操作。
  共同体 union 改变任意一个成员的二进制值, 其余成员的值都会发生变化。考虑如下代码:


union TestUnion {int     m_int_value;float   m_float_value;
};int main() {TestUnion test_un;test_un.m_int_value = 666;std::cout << "Integer Value: " << test_un.m_int_value << "\n";test_un.m_float_value = 3.14f;std::cout << "Float Value: " << test_un.m_float_value << "\n";std::cout << "Integer Value: " << test_un.m_int_value << "\n";return 0;
}   

 此时输出如下所示, 当对以上代码中的浮点数成员进行修改后, 对应整数成员的二进制发生了变化, 此时访问整数类型成员是无效的, 但是访问整数型成员的没有任何警告或者异常。

Integer Value: 666
Float Value: 3.14
Integer Value: 1078523331

  除此之外还有一个问题, 那就是 C++ 里面有很多复杂类型, 当我们向共用体里新增复杂类型后, 维护起来就比较困难了。考虑如下代码:

union TestUnion {int     m_int_value;float   m_float_value;std::string m_string_value;
};

当向共用体里面添加一个 std::string 类型时, 直接编译不通过. 编译器无法判断该如何构造 std::string, 析构的时候该不该析构 std::string 成员, 所以它就方了. 原先存的都是POD类型,它们是没有构造和析构调用的, 如果想要编译通过就得手动维护共用体的构造和析构函数, 这就复杂了。

综上所述, 需要引入 std::variant.

二、std::variant 用法简介
2.1、类型安全保证

 先来看看它的类型安全保证, 代码如下:

int main() {std::variant<int, float> test_variant;test_variant = 10;std::cout << std::get<int>(test_variant) << std::endl;test_variant = 10.3f;std::cout << std::get<float>(test_variant) << std::endl;std::cout << std::get<int>(test_variant) << std::endl;return 0;
}

输出如下:

10
10.3
terminate called after throwing an instance of ‘std::bad_variant_access’
what(): std::get: wrong index for variant
Aborted

写入浮点数后再访问整数类型成员, 直接抛出了异常, 这提供了一个类型安全的保证。除此之外, 它能自动维护C++ 当中的复杂类型,也就是构造和析构函数的调用。

int main() {std::variant<int, float, std::string> test_variant;test_variant = 10;std::cout << std::get<int>(test_variant) << std::endl;test_variant = 10.3f;std::cout << std::get<float>(test_variant) << std::endl;test_variant = "test_variant";std::cout << std::get<std::string>(test_variant) << std::endl;return 0;
}

10
10.3
test_variant

2.2、访问 std::variant

 这里提供两种访问方式, 一种是预先的类型检测, 一种是采用指针的方式.

int main() {std::variant<int, float, std::string> test_variant;test_variant = "test_variant";if (std::holds_alternative<int>(test_variant) ){std::cout<<"hold int:" << std::get<int>(test_variant) <<std::endl;}else if(std::holds_alternative<float>(test_variant) ){std::cout<<"hold float:" << std::get<float>(test_variant) <<std::endl;}else if(std::holds_alternative<std::string>(test_variant) ){std::cout<<"hold std:;stirng:" << std::get<std::string>(test_variant) <<std::endl;}
}

hold std:;stirng:test_variant

基于指针的方式,代码如下:

    std::variant<int, float, std::string> test_variant;test_variant = 100.f;if (auto ptr = std::get_if<int>(&test_variant) ){std::cout<<"hold int:" << *ptr <<std::endl;}else if(auto ptr = std::get_if<float>(&test_variant) ){std::cout<<"hold float:" << *ptr <<std::endl;}else if(auto ptr = std::get_if<std::string>(&test_variant) ){std::cout<<"hold std:;stirng:" << *ptr <<std::endl;}

hold float:100

cppreference 里面还提供了一种更牛逼的遍历方式, 代码如下:


using var_t = std::variant<int, long, double, std::string>;/*
这里首先是声明一个可变模板参数的函数模板 overloaded, 再显示的推导为 overloaded 结构体
overloaded 也是一个可变模板参数的模板结构体, 通过继承所有模板参数, 再结合 using Ts::operator()...
实现对基类, 也就是模板参数中的 operator() 运算符的声明。 此时就可以根据 vec 列表的成员类型调用合适的 lambda 表达式了
*/template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;int main()
{std::vector<var_t> vec = {10, 15l, 1.5, "hello"};for (auto& v: vec) {std::visit(overloaded{[](auto arg) { std::cout << arg << ' '; },[](double arg) { std::cout << std::fixed << arg << ' '; },[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }}, v);}
}
三、std::variant vs OO

  std::variant 在某种情况下可以作为多态的一种替换选择。


namespace{int sum_count = 0;  const int kShapeCount {50000};
};class Shape
{
public:virtual ~Shape() =default;virtual void DoSomething() = 0;
};class Cricle:public Shape
{
public:void DoSomething() override;
};class Rectange:public Shape
{
public:void DoSomething()override;
};class CricleEX
{
public:void DoSomething(){};
};class RectangeEX
{
public:void DoSomething(){};
};int main() {std::vector<std::variant<CricleEX, RectangeEX> > ex_shape_vecs;for(int i{0}; i<kShapeCount; i++){ex_shape_vecs.emplace_back(CricleEX() );ex_shape_vecs.emplace_back(RectangeEX() );}auto t1 = std::chrono::steady_clock::now();for(auto& itor: ex_shape_vecs){std::visit([](auto& obj){obj.DoSomething();}, itor);}auto t2 = std::chrono::steady_clock::now();std::cout << "std::varint cost:" <<std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t2-t1).count() <<"ms" <<std::endl;std::vector<std::unique_ptr<Shape> > shape_vecs;for(int i{0}; i<kShapeCount; i++){shape_vecs.emplace_back(std::make_unique<Cricle>() );shape_vecs.emplace_back(std::make_unique<Rectange>() );}auto t3 = std::chrono::steady_clock::now();for(const auto& itor: shape_vecs){itor->DoSomething();}auto t4 = std::chrono::steady_clock::now();std::cout << "OO cost:" <<std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t4-t3).count() <<"ms" <<std::endl;std::cout<<"sumcount:"<<sum_count<<std::endl;return 0;
}

std::varint cost:7.94071ms
OO cost:4.19324ms
sumcount:500000

很奇怪,这里我得到的结论是面向对象的方式要比 std::variat 效率高, 这与吴咏伟老师在课堂上的当时出现了相反的结论, 这个难道是编译优化选项有啥不同哇, 大家觉得呢?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/4190.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【介绍下IDM的实用功能】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

【已解决】仅当从 VS 开发人员命令提示符处运行 VS Code 时,cl.exe 生成和调试才可用。

当我们在使用vs code运行.c文件时可能会出现如下报错&#xff1a; 这是因为我们使用的生成和调试方式是cl.exe&#xff0c;我们需要更改到gcc.exe。 解决办法&#xff1a; 将所需运行的文件复制粘贴到新的一个文件夹下&#xff0c;此时再点击运行按钮会弹出如下窗口&#xf…

Vue入门到关门之计算属性与监听属性

一、计算属性 1、什么是计算属性 计算属性是基于其它属性计算得出的属性&#xff0c;就像Python中的property&#xff0c;可以把方法/函数伪装成属性&#xff0c;在模板中可以像普通属性一样使用&#xff0c;但它们是基于响应式依赖进行缓存的。这意味着只有在依赖的响应式数…

Mysql 存在多条数据,按时间取最新的那一组数据

1、数据如下&#xff0c;获取每个用户最近的一次登录数据 思路1&#xff1a;order by group by 先根据UserIdLogInTime排序&#xff0c;再利用Group分组&#xff0c;即可得到每个User_Id的最新数据。 1 SELECT * FROM login_db l ORDER BY l.user_id, l.login_time DESC; 排…

基于车载点云数据的城市道路特征目标提取与三维重构

作者&#xff1a;邓宇彤&#xff0c;李峰&#xff0c;周思齐等 来源&#xff1a;《北京工业大学学报》 编辑&#xff1a;东岸因为一点人工一点智能公众号 基于车载点云数据的城市道路特征目标提取与三维重构本研究旨在弥补现有研究在处理复杂环境和大数据量上的不足&#xf…

MFC实现ini配置文件的读取

MFC实现 ini 配置文件的读取1 实现的功能&#xff1a;点击导入配置文件按钮可以在旁边编辑框中显示配置文件的路径&#xff0c;以及在下面的编辑框中显示配置文件的内容。 1. 显示配置文件内容的编辑框设置 对于显示配置文件内容的 Edit Contorl 编辑框的属性设置如下&#x…

在Docker容器中部署LibreOffice:通过Dockerfile定制高效办公环境

随着容器技术的普及,Docker已成为快速部署和管理应用的首选工具。LibreOffice作为一款开源的办公套件,以其丰富的功能和兼容性受到许多企业和个人用户的青睐。将LibreOffice集成到Docker容器中,不仅便于跨平台部署,还能实现资源的隔离和管理,特别适合云环境下的文档处理服…

绘唐3怎么联系团长299矩阵反推模块使用说明

反推配置说明看这里:团长https://qvfbz6lhqnd.feishu.cn/wiki/D3YLwmIzmivZ7BkDij6coVcbn7W MJ配置说明 如上图 选择公有云,即可体验

Linux计划任务书以及定时任务的编写

一、程序可以通过两种方式执行&#xff1a; 手动执行利用调度任务&#xff0c;依据一定的条件自动执行 自动执行可通过一下两个命令来实现: &#xff08;1&#xff09;At &#xff08;单一工作调度&#xff09; &#xff08;2&#xff09;Cron &#xff08;循环工作调度&a…

JavaScript 中 ES6

在ES6&#xff08;ECMAScript 2015&#xff09;中&#xff0c;JavaScript引入了一些新的语法和特性来支持面向对象编程&#xff08;OOP&#xff09;。下面是对ES6中面向对象编程的详细解释&#xff1a; 类&#xff08;Class&#xff09;&#xff1a; ES6引入了类的概念&#xf…

HTML实体编码

HTML实体编码是HTML中用来替换特殊字符的一种机制&#xff0c;以确保这些特殊字符在浏览器中能够正确显示 这些特殊字符在HTML中具有特定的含义&#xff0c;比如小于号“<”用来表示HTML标签的开始&#xff0c;大于号“>”用来表示HTML标签的结束&#xff0c;而引号可能…

求三个字符数组最大者(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h> # include <string.h>int main() {//初始化变量值&#xff1b;int i 0;char str[3][20];char string[20];//循环输入3个字符…

Selenium的四种部署方式

关于selenium 的部署&#xff0c;我在网上找了很多&#xff0c;基本上都没有提到或是说的比较清晰的。当时我一直有个困惑&#xff1a;测试的脚本代码&#xff0c;是放在跟浏览器同一台机器上呢&#xff0c;还是放在Application Server上&#xff1f; 在官方开发文档中&#x…

蛋糕购物商城

蛋糕购物商城 运行前附加数据库.mdf&#xff08;或使用sql生成数据库&#xff09; 登陆账号&#xff1a;admin 密码&#xff1a;123456 修改专辑价格时去掉&#xffe5;以及上传专辑图片 c#_asp.net 蛋糕购物商城 网上商城 三层架构 在线购物网站&#xff0c;电子商务系统 …

Kubernetes中文件挂载的四种方式

一. Kubernetes入门 1.1 Kubernetes创建POD过程 1.2. Kubernetes基本操作 命令说明用法create创建kubectl create -f xx.yamledit编辑kubectl edit svc [POD的service名称]get获取kubectl get pod --namespaceXXXpatch更新kubectl patch -f xx.yamldelete删除kubectl delete …

Golang基础8-go语言依赖管理

go语言依赖管理 探索 GO 项目依赖包管理与Go Module常规操作 - 知乎 https://juejin.cn/post/7054513615625256996 演进过程&#xff1a; GOPATH机制 早期引入GOPATH机制&#xff0c;Go 编译器可以在本地 GOPATH 环境变量配置的路径下&#xff0c;搜寻 Go 程序依赖的第三方…

探索前端开发新利器:MFSU

在前端开发领域&#xff0c;随着项目规模的不断扩大和对性能优化需求的日益增长&#xff0c;开发者们一直在寻找更高效、更智能的构建工具。其中&#xff0c;MFSU&#xff08;Module Federation for Super Ultra Fast&#xff09;作为一种新兴的前端构建加速方案&#xff0c;以…

AWTK 开源串口屏开发(17) - 通过 MODBUS 访问数组数据

在 AWTK 串口屏中&#xff0c;内置了 MODBUS Client Channel 的模型&#xff0c;不用编写代码即可实现在 ListView 中显示数组数据。 MODBUS 协议一次只能读取 125 个 WORD&#xff0c;AWTK-MODBUS Client Channel 支持长数据&#xff0c;自动分成多个请求访问。 1. 功能 不用…

JWT介绍和使用

JWT介绍和使用 JWT介绍 JWT(JSON Web Token)是一个开放的标准&#xff08;RFC 7519&#xff09;&#xff0c;JWT定义了一种简介的、自包含的协议格式。可以用于在通信的双方传递json对象&#xff0c;传递的信息可以被信任&#xff0c;因为信息是被数字签名的。JWT可以使用HMA…

解决python3.10以上pyqt6-tools无法安装问题

情景描述 原本3.9版本python用的好好地&#xff0c;最新的一个自动化库要求必须要3.10以上才能使用。 火急火燎更新3.12版本python&#xff0c;结果安装qt-tools丫的安装不了了。 问题出现原因 python的pyqt-tools他不支持3.10以上的python版本下载。 如果想用pip下载得py…