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

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;56 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, int16     std::variant<Person,int> vpi; //并没有显示指明存入一个Person, 而实际却是存入了Person

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

 704       constexpr705       _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;56 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;724725   template<typename _Tp, typename _Tuple>726     inline constexpr size_t __tuple_count_v =727       __tuple_count<_Tp, _Tuple>::value;728729   template<typename _Tp, typename... _Types>730     struct __tuple_count<_Tp, tuple<_Types...>>731     : integral_constant<size_t, 0> { };732733   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>> { };738739   // 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> {};171172   template<typename _Tp, typename... _Types>173     inline constexpr size_t __index_of_v = __index_of<_Tp, _Types...>::value;174175   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.hqwc.cn/news/485072.html

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

相关文章

ChatGPT丨“成像光谱遥感技术中的AI革命:ChatGPT应用指南“

遥感技术主要通过卫星和飞机从远处观察和测量我们的环境&#xff0c;是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型&#xff0c;在理解和生成人类语言方面表现出了非凡的能力。本文重点介绍ChatGPT在遥感中的应用&#xff0c;人工智能…

时序预测demo 代码快速实现 MLP效果比LSTM 好,简单模拟数据

【PyTorch修炼】用pytorch写一个经常用来测试时序模型的简单常规套路&#xff08;LSTM多步迭代预测&#xff09; 层数的理解&#xff1a; LSTM&#xff08;长短期记忆&#xff09;的层数指的是在神经网络中堆叠的LSTM单元的数量。层数决定了网络能够学习的复杂性和深度。每一层…

网络安全-pikachu之文件上传漏洞2

进入到第二个文件上传漏洞&#xff0c;发现名字是MIME type&#xff0c;并且查看前端源代码没发现限制&#xff0c;所以是后段&#xff0c;盲猜通过抓包就可以绕过后段限制。 先知道MIME type是什么&#xff0c;通过查找资料发现是&#xff1a;Content-Type是返回消息中非常重…

SpringCloud(15)之SpringCloud Gateway

一、Spring Cloud Gateway介绍 Spring Cloud Gateway 是Spring Cloud团队的一个全新项目&#xff0c;基于Spring 5.0、SpringBoot2.0、 Project Reactor 等技术开发的网关。旨在为微服务架构提供一种简单有效统一的API路由管理方式。 Spring Cloud Gateway 作为SpringCloud生态…

jquery写组件滑动人机验证组件

jquery组件&#xff0c;虽然 jquery 语法古老&#xff0c;但是写好了用起来真的很爽啊&#xff0c;本文用滑动人机验证给大家做个详细教程&#xff08;直接复制代码就可以用噢o(*&#xffe3;▽&#xffe3;*)ブ&#xff09; 第一步 先看下组件本身 component.js (function() {…

Rust介绍与开发环境搭建

安装rust rust 安装官方指南&#xff1a;[HTPS][3W].rust-lang.org/tools/install &#xff08;自己替换 HTPS,3W&#xff09; Linux或者Macbook上安装rust 打开终端并输入下面命令&#xff1a; #因审核问题下面链接需要替换一下 HTPS->httpscurl --tlsv1.2 [HTPS]://s…

Windows Server 2019修改网络位置为公用网络

Windows Server 2019修改网络位置与Windows有点点区别&#xff0c;特记录如下列图&#xff1a;

我是这样通过CATTI考试的,没办法,必须考!原创首发

2023年“侥幸”通过CATTI英语二级笔译。11月初考试&#xff0c;按官方原计划应该是2024年1月初公布考试成绩&#xff0c;但12月底就突然出分了。当时正好在上班&#xff0c;忙里偷闲登录网址、查分&#xff0c;没有想象中的那么激动&#xff0c;一切平淡如水。随后&#xff0c;…

基于springboot+vue的知识管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

Linux篇:进程

一. 前置知识 1.1冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系 为什么计算机要采用冯诺依曼体系呢&#xff1f; 在计算机出现之前有很多人都提出过计算机体系结构&#xff0c;但最…

【国产MCU】-CH32V307-通用定时器(GPTM)-单脉冲模式

通用定时器(GPTM)-单脉冲模式 文章目录 通用定时器(GPTM)-单脉冲模式1、单脉冲模式介绍2、驱动API介绍3、单脉冲使用实例本文将详细介绍如何使用CH32V307通用定时器的单脉冲模式。 1、单脉冲模式介绍 单脉冲模式可以响应一个特定的事件,在一个延迟之后产生一个脉冲,延迟…

关于参数处理那点事,C标准库反汇编解析

关于参数处理那点事&#xff0c;C标准库反汇编解析 1 stdarg.h 内容概览 这个头文件用于提供访问无名参数&#xff08;既没有命名也没有类型&#xff09;的类型和宏。 假设函数形如: void functionWithMltipleInput(normalType n, ...)第一个参数名为n&#xff0c;后续省略号…