Modern C++ std::variant的5个特性+原理

news2024/11/16 2:43:06

1 前言

上一节《Modern C++ std::variant的实现原理》我们简单分析了std::variant的实现原理,其实要学好C++编程,除了看优秀的代码包括标准库实现,读文档也是很便捷且必须的一种办法。
本节我将逐条解析文档中的五个特性,解析的办法有两种:实现代码讲解、用例子举例。

2 文档

variant文档
在这里插入图片描述

3解析

以下所有实现代码都来自/usr/include/c++/11/variant。

3.1 类型安全

The class template std::variant represents a type-safe union.
An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error - no value (this state is hard to achieve, see valueless_by_exception).
类型安全,且总会持有一种类型的值,但也有极小的可能无值(valueless)。
无值请参考文档, 我们重点说下类型安全。
咱们先说下union怎么类型不安全,比如下面的例子:

    union Data {
        int intValue;
        double doubleValue;
     };
     Data d;
     d.intValue = 10;
     cout<<d.doubleValue; //类型不安全,存入int,取出double

但variant你做不到这样:

    std::variant<int, double> v;
     v = 1;
     cout << get<1>(v)<<endl;

编译没问题,但运行报异常:

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

这是因为当前存储了什么类型是被_M_index记了下来的,在我们的例子中存了int,故_M_index=0, 而double是下一个类型其_M_index=1. 实现代码如下:

1672   template<size_t _Np, typename... _Types>
1673     constexpr variant_alternative_t<_Np, variant<_Types...>>&
1674     get(variant<_Types...>& __v)
1675     {
1676       static_assert(_Np < sizeof...(_Types),
1677             "The index must be in [0, number of alternatives)");
1678       if (__v.index() != _Np) //index()返回_M_index
1679     __throw_bad_variant_access(__v.valueless_by_exception()); //本例触发异常
1680       return __detail::__variant::__get<_Np>(__v);
1681     }

当然,类型安全的代价就是需要比union多点内存存_M_index。它的类型可能是char, 也可能是short, 这取决于你的variant声明时要容纳的类型个数:

401   template <typename... _Types>
 402     using __select_index =
 403       typename __select_int::_Select_int_base<sizeof...(_Types),
 404                           unsigned char,
 405                           unsigned short>::type::value_type;
446       _Variadic_union<_Types...> _M_u;
 447       using __index_type = __select_index<_Types...>;
 448       __index_type _M_index;
 449     };

3.2 默认持有第一个类型的值

a default-constructed variant holds a value of its first alternative, unless that alternative is not default-constructible
我们先通过一个例子有个直观的认识:

  1 #include <iostream>
  2 #include <variant>
  3 #include <string>
  4 using namespace std;
  5
  6 int main() {
  7     class Person{
  8         public:
  9         Person(){
 10             char ch;
 11             std::cout << "Enter a character: ";
 12             std::cin.get(ch);
 13         }
 14     };
 15     // Define a variant with 2 alternatives: Person, int
 16     std::variant<Person,int> vpi; //并没有显示指明存入一个Person, 而实际却是存入了Person

我让程序卡在输入那(第12行),方便用GDB看下调用栈
在这里插入图片描述
关键代码在栈第14、13层:

 704       constexpr
 705       _Variant_base()
 706       noexcept(_Traits<_Types...>::_S_nothrow_default_ctor)
 707       : _Variant_base(in_place_index<0>) { }  //类型列表中的第一个类型即Person

**思考:**如果第一个类型没有默认构造函数哪?
答案也在文档中

unless that alternative is not default-constructible

把上面的默认构造函数置为delete, 编译出错:
在这里插入图片描述
还记得上节preview的图吗?继承_Enable_default_constructor的原因也在这里(有机会再细讲)

3.3 内存开始即分配好,没有动态分配内存

As with unions, if a variant holds a value of some object type T, the object representation of T is allocated directly within the object representation of the variant itself. Variant is not allowed to allocate additional (dynamic) memory.
这一点我们上一节已经提到过,不在赘述。

3.4 不能存入引用,数组,void

A variant is not permitted to hold references, arrays, or the type void.
实现代码有如下片段:

1346       static_assert(sizeof...(_Types) > 0,
1347             "variant must have at least one alternative");
1348       static_assert(!(std::is_reference_v<_Types> || ...),
1349             "variant must have no reference alternative");
1350       static_assert(!(std::is_void_v<_Types> || ...),
1351             "variant must have no void alternative");

显然引用、void已经被禁。而原生数组不是完整类型,不能在标准库容器中被用于模板参数。

3.5 可以重复持有相同类型

A variant is permitted to hold the same type more than once, and to hold differently cv-qualified versions of the same type.
可以持有多个相同类型,比如两个int

  1 #include <iostream>
  2 #include <variant>
  3 #include <string>
  4 using namespace std;
  5
  6 int main() {
  7     std::variant<int,int> v2i;
  8     v2i.emplace<0>(1);
  9     //cout<<get<1>(v2i)<<endl; //虽然类型相同,但依然不能按第二个int取值
 10     v2i.emplace<1>(2);
 11     cout<<get<1>(v2i)<<endl;

3.6 get by type

这一条在get部分
在这里插入图片描述
获得数据不仅仅能用下标,还能用类型,比如

    std::variant<int, double> v;
     v = 1;
     cout << get<double>(v)<<endl;

后台还是找到double的下标取得数据。如何转的哪?先不要急,让我们先看看3.4中提到的例子

  7     std::variant<int,int> v2i;
  8     v2i.emplace<0>(1);
  9     //cout<<get<1>(v2i)<<endl; //虽然类型相同,但依然不能按第二个int取值
 10     v2i.emplace<1>(2);
 11     cout<<get<int>(v2i)<<endl; //get<n> 改为get<int>

这会导致编译出错,
在这里插入图片描述
显然它区分不出来你要去第几个int, 它也不允许这么用:

1116   template<typename _Tp, typename... _Types>
1117     constexpr _Tp& get(variant<_Types...>& __v)
1118     {
1119       static_assert(__detail::__variant::__exactly_once<_Tp, _Types...>,
1120             "T must occur exactly once in alternatives");
1121       static_assert(!is_void_v<_Tp>, "_Tp must not be void");
1122       return std::get<__detail::__variant::__index_of_v<_Tp, _Types...>>(__v);
1123     }

我们例子中_Tp=int, _Types={int,int}, _Tp在_Types中出现了两次,导致__exactly_once是false, 所以报了1119行的T must occur exactly once in alternatives
__exactly_once的实现又是一个递归哈。

 721   // For how many times does _Tp appear in _Tuple?
 722   template<typename _Tp, typename _Tuple>
 723     struct __tuple_count;
 724
 725   template<typename _Tp, typename _Tuple>
 726     inline constexpr size_t __tuple_count_v =
 727       __tuple_count<_Tp, _Tuple>::value;
 728
 729   template<typename _Tp, typename... _Types>
 730     struct __tuple_count<_Tp, tuple<_Types...>>
 731     : integral_constant<size_t, 0> { };
 732
 733   template<typename _Tp, typename _First, typename... _Rest>
 734     struct __tuple_count<_Tp, tuple<_First, _Rest...>>
 735     : integral_constant<
 736     size_t,
 737     __tuple_count_v<_Tp, tuple<_Rest...>> + is_same_v<_Tp, _First>> { };
 738
 739   // TODO: Reuse this in <tuple> ?
 740   template<typename _Tp, typename... _Types>
 741     inline constexpr bool **__exactly_once** =
 742       __tuple_count_v<_Tp, tuple<_Types...>> == 1;

回到正常,如果1119行不报错,则来到

1122       return std::get<__detail::__variant::__index_of_v<_Tp, _Types...>>(__v);

查找_Tp在_Types中的下标,其实现也是递归,又是递归啊:

167   // Returns the first appearance of _Tp in _Types.
 168   // Returns sizeof...(_Types) if _Tp is not in _Types.
 169   template<typename _Tp, typename... _Types>
 170     struct __index_of : std::integral_constant<size_t, 0> {};
 171
 172   template<typename _Tp, typename... _Types>
 173     inline constexpr size_t __index_of_v = __index_of<_Tp, _Types...>::value;
 174
 175   template<typename _Tp, typename _First, typename... _Rest>
 176     struct __index_of<_Tp, _First, _Rest...> :
 177       std::integral_constant<size_t, is_same_v<_Tp, _First>
 178     ? 0 : __index_of_v<_Tp, _Rest...> + 1> {};

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

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

相关文章

webstorm光标变成方块解决办法_webstorm光标变粗不能换行

webstorms光标变了 键盘上的insert是切换的快捷键&#xff0c;敲insert就可以来回切换了

C++之Easyx——图形库的基本功能(1):界面操作

最近&#xff0c;我觉得使用控制台编写游戏太没意思了&#xff01;&#xff01; 所以我开始研究图形库了~ 一、setinitmode 函数定义 void EGEAPI setinitmode(int mode, int x CW_USEDEFAULT, int y CW_USEDEFAULT); //设置初始化模式&#xff0c;mode0为普通&#xff0c…

公司网站建设费用怎么算,花销影响因素有哪些?

企业网站建设成本如何计算&#xff1f; 建立一个网站需要多少钱&#xff1f; 这个问题的答案并不容易说出来。 一般来说&#xff0c;网站建设分为模板网站和定制网站。 如果想要价格低廉&#xff0c;就选择模板建站&#xff0c;如果想要强大的推广营销功能&#xff0c;就选择定…

【数据库】达梦数据库DM8开发版安装

目录 一、达梦数据库概述 1.1 达梦数据库简介 1.2 产品特性 1.3 产品架构 二、安装前准备 2.1 新建 dmdba 用户 2.2 修改文件打开最大数 2.3 挂载镜像 2.4 新建安装目录 2.5 修改安装目录权限 三、数据库安装 3.1 命令行安装 3.2 配置环境变量 四、配置实例 4.1…

OLED透明屏厂家:开启2024年新征程

随着科技的不断进步和创新&#xff0c;OLED透明屏作为一种前沿的显示技术&#xff0c;正逐渐走进人们的视野&#xff0c;成为多个领域的焦点。在2024年2月21日这个特殊的日子&#xff0c;我们这家领先的OLED透明屏厂家正式开工&#xff0c;预示着我们将迎来一个充满机遇和挑战的…

二建过来人的大实话,信息闭塞真的很致命!

球球了&#xff0c;别像个大冤种一样&#xff0c;还没了解清楚就开始盲目的备考&#xff0c;真的会输的很惨.... 作为自信满满却一站失利的过来人&#xff0c;我有话要讲&#xff01;谁再说二建简单&#xff0c;就让她替我考 几个重要时间点&#xff1a; 报名开始&#xff1a…

Dynamo批量操作之提取族库族参数写入Excel

你好&#xff0c;我是九哥~ 今天&#xff0c;来开一个新的系列教程——Dynamo批量操作。 批量操作&#xff0c;可以说是Dynamo最擅长做的事&#xff0c;可以批量改参数值&#xff0c;批量放置族等等&#xff0c;但是批量操作起复杂的数据结构&#xff0c;还是需要一些技巧的&…

Java项目:24 基于SpringBoot+freemarker实现的人事管理系统

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 基于SpringBootfreemarker实现的人事管理系统分为七大模块&#xff1a;绩效考核&#xff0c;招聘管理&#xff0c;档案管理&#xff0c;工资管理&…

世界上最简单的无解问题

1990年的Cleve’s Corner专栏文章《世界上最简单的无解问题》中描述了压缩感知遇到的问题的一个简化版本。 例如&#xff0c;两个平均值为3的数字&#xff0c;这些数字是什么&#xff1f; 在我们抱怨没有足够的信息后&#xff0c;可能会回答2和4。 如果我们这样做了&#xff0c…

并发编程入门指南

文章目录 并发编程进程和线程的区别并发和并行的区别创建线程的方式线程之间的状态&#xff0c;状态之间的转换新建三个线程&#xff0c;如何保证按顺序执行wait方法和sleep的区别如何停止一个正在运行的线程synchronized关键字底层原理Monitor属于重量级锁&#xff0c;了解过锁…

Stable Diffusion 模型分享:A-Zovya RPG Artist Tools(RPG 大师工具箱)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 A-Zovya RPG Artist Tools 模型是一个针对 RPG 训练的一个模型&#xff0c;可以生成一些 R…

基于springboot + vue实现的前后端分离-酒店管理系统

项目介绍 基于springboot vue实现的酒店管理系统一共有酒店管理员和用户这两种角色。 管理员功能 登录&#xff1a;管理员可以通过登录功能进入系统&#xff0c;确保只有授权人员可以访问系统。用户管理&#xff1a;管理员可以添加、编辑和删除酒店的用户&#xff0c;包括前…

最新YOLOv9论文理论:使用可编程梯度信息学习您想学习的内容 | Programmable Gradient Information

YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information YOLOv9 论文地址&#xff1a;https://arxiv.org/pdf/2402.13616.pdf 摘要 当今的深度学习方法侧重于如何设计最合适的目标函数&#xff0c;以便模型的预测结果最接近真实情况。同时&…

精美工作室引导页源码

精美工作室引导页源码 源码介绍&#xff1a;一款精美的工作室引导页源码 下载地址&#xff1a; https://www.changyouzuhao.cn/6560.html

⭐北邮复试刷题LCR 018. 验证回文串__双指针 (力扣119经典题变种挑战)

LCR 018. 验证回文串 给定一个字符串 s &#xff0c;验证 s 是否是 回文串 &#xff0c;只考虑字母和数字字符&#xff0c;可以忽略字母的大小写。 本题中&#xff0c;将空字符串定义为有效的 回文串 。 示例 1: 输入: s “A man, a plan, a canal: Panama” 输出: true 解释…

正交匹配追踪(Orthogonal Matching Pursuit, OMP)的MATLAB实现

压缩感知&#xff08;Compressed Sensing, CS&#xff09;是一种利用稀疏信号的先验知识&#xff0c;用远少于奈奎斯特采样定理要求的样本数目恢复整个信号的技术。正交匹配追踪&#xff08;Orthogonal Matching Pursuit, OMP&#xff09;是一种常见的贪婪算法&#xff08;Gree…

沁恒CH32V30X学习笔记02--GPIO的使用教程及2次封装驱动

gpio 概述 刚复位后,GPIO 口运行在初始状态,这时大多数 IO 口都是运行在浮空输入状态 外部中断 所有的 GPIO 口都可以被配置外部中断输入通道,但一个外部中断输入通道最多只能映射到一个 GPIO 引脚上,且外部中断通道的序号必须和 GPIO 端口的位号一致,比如 PA1(或 PB1、…

windows下快速安装nginx 并配置开机自启动

1、下载地址&#xff1a;http://nginx.org/en/download.html 2、启动nginx 注意⚠️ 不要直接双击nginx.exe&#xff0c;这样会导致修改配置后重启、停止nginx无效&#xff0c;需要手动关闭任务管理器内的所有nginx进程。 在nginx.exe目录&#xff0c;打开命令行工具&#xf…

Linux搭建FISCO BCOS的第一个区块链网络

一、前言 FISCO BCOS是由金融区块链合作联盟&#xff08;深圳&#xff09;与微众银行共同发起的开源区块链项目&#xff0c;支持多链多账本&#xff0c;满足金融行业复杂业务需求。本文将介绍如何在Ubuntu操作系统上使用Linux命令搭建FISCO BCOS的第一个区块链网络。 目录 一…

Jetpack Compose -> 重组作用域和remember()

前言 上一章我们讲解了 MutableState 和 mutableStateOf() 本章我们讲解下 remember 这个关键方法&#xff1b; ReCompose Scope(重组作用域) 我们先来看一段代码 当我们将 var name by mutableStateOf("老A")lifecycleScope.launch{}这两行代码放到 setContent 中…