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

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.mzph.cn/news/704639.shtml

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

相关文章

H12-821_77

77.如图所示的交换网络&#xff0c;所有交换机都运行了STP协议&#xff0c;当拓扑稳定后&#xff0c;在以下哪台交换机上修改配置BPDU的发送周期&#xff0c;可以影响STD配置BPDU的发送周期&#xff1f; A.STC B.SWD C.SWA D.SWB 答案&#xff1a;C 注释&#xff1a; 在根桥上…

(十九)devops持续集成开发——jenkins的一些常用插件和工具的安装

前言 本节内容会着重介绍jenkins持续集成开发工具的一些常用插件安装以及全局工具的配置安装&#xff0c;并说明其主要作用。在开始插件和工具安装之前&#xff0c;我们要保证可以正常访问网络&#xff0c;并且使用国内的插件更新地址&#xff0c;便于插件的正常安装。官方的地…

【JavaEE】网络原理: HTTPS协议相关内容

目录 HTTPS 是什么 HTTPS 的工作过程 对称加密 非对称加密 引入证书 理解数据签名 通过证书解决黑客攻击 HTTPS 是什么 HTTPS也是一个应用层协议, 是在HTTP协议的基础上引入了一个加密层. HTTP协议内容都是按照文本的方式明文传输的, 这就导致在传输过程中出现一些被篡…

蜂邮EDM-新手教程-新手也能使用

一、登录注册账号&#xff0c;注册登录地址&#xff1a;fengemail.com 二、配置邮箱 选择“账号设置”——“邮箱设置”进行发信邮箱配置。每个账号将默认存在一个“系统默认接口”&#xff0c;点击右侧的编辑按钮即可对该配置进行修改。 注&#xff1a;发信邮箱暂不支持个人…

抖音数据抓取工具|抖音视频下载工具

抖音数据抓取工具是一款基于C#开发的高效实用软件&#xff0c;旨在为用户提供便捷的抖音视频数据获取和处理功能。该工具不仅支持通过关键词进行搜索抓取&#xff0c;还能够通过分享链接进行单个视频的抓取和下载&#xff0c;为用户提供了多样化的数据采集方式。 主要功能模块…

SpringMVC 学习(四)之获取请求参数

目录 1 通过 HttpServletRequest 获取请求参数 2 通过控制器方法的形参获取请求参数 3 通过 POJO 获取请求参数&#xff08;重点&#xff09; 1 通过 HttpServletRequest 获取请求参数 public String handler1(HttpServletRequest request) <form action"${pageCont…

安装python的docker库

文章目录 一、在线安装二、制作离线安装包2.1 报错处理 一、在线安装 先确定是否有pip命令。 yum install python-pip直接安装。 pip install docker查看docker库。 pip list二、制作离线安装包 在有互联网的环境下直接安装。 #docker为下载下来的包名。 pip download do…

typecho 给文章创建目录树

受益于 shortcode 短代码插件和泽泽短代码中目录树的显示样式&#xff0c;形成了自己实现添加文章目录的思路&#xff1a; 一、文章目录树的结构 <div id"toc"><div class"toc-left"><div class"toc-btn" type"button&quo…

搜维尔科技:用于运动科学的 OptiTrack,范围标记、步态捕捉!

OptiTrack 系统提供世界领先的测量精度和简单易用的工作流程&#xff0c;为研究人员和生物力学师的研究提供理想的 3D 跟踪数据。 对所有主要数字测力台、EMG 和模拟设备的本机即插即用支持为研究人员提供了在 Visual3D、MotionMonitor、MATLAB 和其他第三方生物力学软件包中进…

Android加载富文本

直接用webview加载&#xff1a; package com.example.testcsdnproject;import androidx.appcompat.app.AppCompatActivity;import android.annotation.SuppressLint; import android.graphics.Color; import android.os.Bundle; import android.util.Log; import android.webk…

Nexus Repository Manager

Nexus Repository Manager https://s01.oss.sonatype.org/#welcome https://mvnrepository.com/-CSDN博客

Ubuntu22.04环境下载安装中文搜狗输入法

0、查看CPU系统架构 确定架构后&#xff0c;下载对应的安装包&#xff0c;否则无法正常安装应用程序 1、进入搜狗拼音输入法官网&#xff0c;下载搜狗输入法 搜狗输入法-首页搜狗拼音输入法官网下载&#xff0c;荣获多个国内软件大奖的搜狗拼音输入法是一款打字更准、词库更大…

3、函数定义,函数调用,this指向总结,闭包

一、函数的定义方式 1、函数声明 function demo1() {var num 12var result Math.pow(num,2)//指数函数return result }2、函数表达式 var demo2 function (x,y) { //内置对象arguments前面的两个参数 是 x,yvar sum arguments[0] arguments[1]console.log(sum) }3、构…

精品基于SpringBoot+Vue的常规应急物资管理系统

《[含文档PPT源码等]精品基于SpringBootVue的常规应急物资管理系统[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#xff…

计网Lesson15 - TCP可靠传输

文章目录 1. 停止等待ARQ协议2. 连续ARQ协议与滑动窗口协议 1. 停止等待ARQ协议 ARQ&#xff08;Automatic Repeat–reQuest&#xff09;自动重传请求 几种重传情况 发送端丢失 发送方过久没有接收到接收方的确认报&#xff0c;这种情况会触发超时重传机制&#xff0c;发送方…

Facebook的未来蓝图:数字社交的下一个篇章

在数字化时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分。而在众多的社交媒体平台中&#xff0c;Facebook一直处于领先地位&#xff0c;不断探索着数字社交的新领域和新形式。随着科技的不断发展和社会的不断变革&#xff0c;Facebook正在谱写着数字社交的未…

JSON:简介与基本使用

目录 什么是JSON&#xff1f; JSON的基本结构 JSON的基本使用 在JavaScript中使用JSON 创建JSON对象 解析JSON字符串 生成JSON字符串 在其他编程语言中使用JSON 总结 什么是JSON&#xff1f; JSON&#xff0c;全称为JavaScript Object Notation&#xff0c;是一种轻量…

lv21 QT入门与基础控件 1

1 QT简介 QT是挪威Trolltech开发的多平台C图形用户界面应用程序框架 典型应用 2 工程搭建 2.1 新建ui工程 不要写中文路径 2.1 不勾选UI&#xff08;主讲&#xff09; 3 QT信号与槽机制 语法&#xff1a;Connect&#xff08;A, SIGNLA(aaa()), B, SLOT(bbb())&#xff09;…

27.HarmonyOS App(JAVA)可复用列表项的ListContainer

可复用列表项的ListContainer 简短的列表可以通过定向布局实现,但是如果列表项非常多,则使用定向布局就不再合适。如需要创建50个列表项的列表,那么用定向布局实现至少需要创建50个以上的组件了。然而,限于设备屏幕大小的限制,绝大多数组件不会显示在屏幕上,却会占据大量的内存…

C#使用QQ邮箱发送邮件

简介 在c#中发送邮箱我们只需要引入官方提供的命名空间 System.Net.Mail &#xff0c;这个命名空间包含了一系列类&#xff0c;用于创建、配置和发送电子邮件消息。 这个命名空间中的一些主要类包括&#xff1a; MailMessage&#xff1a;表示一个电子邮件消息&#xff0c;包…