当前位置: 首页 > news >正文

C++17 std::variant 详解:概念、用法和实现细节

image.png

文章目录

    • 简介
    • 基本概念
      • 定义和使用std::variant
      • 与传统联合体union的区别
    • 多类型值存储示例
      • 初始化
      • 修改
      • 判断variant中对应类型是否有值
      • 获取std::variant中的值
      • 获取当前使用的type在variant声明中的索引
    • 访问std::variant中的值
      • 使用std::get
      • 使用std::get_if
    • 错误处理和访问未初始化的std::variant
    • 应用场景
      • 解析命令行
      • 解析ini文件
      • 语言解析器
      • 求解方程的根
      • 错误处理
      • 状态机
      • 不使用虚表和继承实现的多态
    • 总结

简介

在C++的发展历程中,C++17带来了许多实用的新特性,其中std::variant尤为引人注目。它本质上是一种类型安全的联合体,能够在同一时刻持有多种可能类型中的某一个值。这种特性为开发者提供了极大的便利,在面对需要处理多种不同类型数据的场景时,std::variant提供了一种灵活且高效的解决方案,使得代码编写更加简洁、安全。

基本概念

定义和使用std::variant

std::variant是一个模板类,借助模板参数包的特性,它能够存储多种不同类型的值。其声明形式如下:

template<class... Types>
class variant;

这里的Types代表了一系列的类型,意味着我们可以根据实际需求,传入任意数量和种类的类型。例如,若要创建一个std::variant对象,使其能够存储intstd::stringdouble类型的值,可以这样定义:

std::variant<int, std::string, double> myVar;

与传统联合体union的区别

传统的C风格联合体union虽然也能实现存储不同类型的值,但与std::variant相比,存在诸多劣势。首先,std::variant具备类型安全性,而union则需要开发者手动管理数据成员的活跃性。在使用union时,如果错误地访问了当前未存储的类型数据,就会导致未定义行为。而std::variant会自动跟踪当前存储的值的类型,开发者无需手动干预。其次,std::variant提供了更为友好和安全的访问方式,使得代码在处理不同类型数据时更加可靠和易于理解。

多类型值存储示例

初始化

std::variant对象的初始化十分便捷。以下面代码为例,创建一个std::variant对象v,并初始化为int类型的值123:

#include <iostream>
#include <variant>int main() {std::variant<int, std::string, double> v(123);return 0;
}

修改

在程序运行过程中,可以根据实际需求修改std::variant对象所存储的值的类型。例如,将上述v的值修改为std::string类型的"HelloWorld":

#include <iostream>
#include <variant>int main() {std::variant<int, std::string, double> v(123);v = "HelloWorld";return 0;
}

判断variant中对应类型是否有值

为了确保类型安全,经常需要判断std::variant中是否存储了特定类型的值。这时,可以使用std::holds_alternative函数来实现:

#include <iostream>
#include <variant>int main() {std::variant<int, std::string, double> v(123);v = "HelloWorld";if (std::holds_alternative<std::string>(v)) {std::cout << "has std::string" << std::endl;}return 0;
}

获取std::variant中的值

获取std::variant中的值主要有两种方式。一种是通过指定类型来获取:

#include <iostream>
#include <variant>int main() {std::variant<int, std::string, double> v("HelloWorld");std::cout << std::get<std::string>(v) << std::endl;return 0;
}

另一种是通过索引来获取,索引从0开始计数:

#include <iostream>
#include <variant>int main() {std::variant<int, std::string, double> v("HelloWorld");std::cout << std::get<1>(v) << std::endl;return 0;
}

获取当前使用的type在variant声明中的索引

通过调用index成员函数,可以获取当前std::variant中存储的值的类型在声明时的索引位置:

#include <iostream>
#include <variant>int main() {std::variant<int, std::string, double> v("HelloWorld");std::cout << v.index() << std::endl;return 0;
}

访问std::variant中的值

使用std::get

std::get是访问std::variant中值的常用方法,如前文示例,它既可以通过指定类型,也能通过索引来获取值。不过,使用时需注意,如果std::variant中当前存储的值并非所指定的类型,会抛出std::bad_variant_access异常。

使用std::get_if

std::get_if是另一种访问std::variant值的方式,它能避免抛出异常。当std::variant中存储的是指定类型的值时,std::get_if会返回一个指向该值的指针;否则,返回nullptr。示例如下:

#include <iostream>
#include <variant>int main() {std::variant<int, std::string, double> v("HelloWorld");if (auto str = std::get_if<std::string>(&v)) {std::cout << *str << std::endl;}return 0;
}

错误处理和访问未初始化的std::variant

std::variant未进行初始化,或者当前存储的值并非期望获取的类型时,调用std::get会抛出std::bad_variant_access异常。例如:

#include <iostream>
#include <variant>int main() {std::variant<int, std::string, double> v(123);try {std::cout << std::get<std::string>(v) << std::endl;} catch (const std::bad_variant_access& e) {std::cerr << "Caught exception: " << e.what() << std::endl;}return 0;
}

而使用std::get_if可以避免这种异常情况的发生,通过检查返回的指针是否为nullptr,来决定是否进行后续操作。

应用场景

解析命令行

在解析命令行参数时,参数可能有多种类型,如整数、字符串等。std::variant可以方便地存储和处理这些不同类型的参数。

解析ini文件

ini文件中的配置项可能有不同的数据类型,std::variant能有效地处理这种多类型数据的解析。

语言解析器

语言解析过程中,词法单元可能有多种类型,如标识符、关键字、常量等。std::variant可以用来存储和管理这些不同类型的词法单元。

求解方程的根

在数值计算中,方程的根可能是实数、复数等不同类型,std::variant可以灵活地存储这些结果。

错误处理

在函数返回值中,可以使用std::variant来同时表示成功结果和错误信息,通过不同的类型来区分。

状态机

状态机的状态可能有多种类型,std::variant可以用于存储和管理这些状态。

不使用虚表和继承实现的多态

通过std::variant结合std::visit(本文未详细介绍),可以实现一种不依赖虚表和继承的多态机制。

总结

std::variant作为C++17的重要特性之一,为开发者提供了强大的功能。它以类型安全和便捷的接口,使得处理多种可能类型的数据变得轻松且安全。在实际编程中,合理运用std::variant,能够显著增强代码的灵活性和可维护性,让代码更加简洁高效。

相关文章:

C++17 std::variant 详解:概念、用法和实现细节

文章目录 简介基本概念定义和使用std::variant与传统联合体union的区别 多类型值存储示例初始化修改判断variant中对应类型是否有值获取std::variant中的值获取当前使用的type在variant声明中的索引 访问std::variant中的值使用std::get使用std::get_if 错误处理和访问未初始化…...

Leetcode::119. 杨辉三角 II

119. 杨辉三角 II 已解答 简单 相关标签 相关企业 给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角」的第 rowIndex 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: rowIndex 3 输出: [1,3,3,1]示例 2: 输入: rowIndex 0…...

多模态论文笔记——TECO

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细解读多模态论文TECO&#xff08;Temporally Consistent Transformer&#xff09;&#xff0c;即时间一致变换器&#xff0c;是一种用于视频生成的创新模型&…...

Ubuntu 16.04用APT安装MySQL

个人博客地址&#xff1a;Ubuntu 16.04用APT安装MySQL | 一张假钞的真实世界 安装MySQL 用以下命令安装MySQL: sudo apt-get install mysql-server 这个命令会安装MySQL服务器、客户端和公共文件。安装过程会出现两个要求输入的对话框&#xff1a; 输入MySQL root用户的密…...

Linux 4.19内核中的内存管理:x86_64架构下的实现与源码解析

在现代操作系统中,内存管理是核心功能之一,它直接影响系统的性能、稳定性和多任务处理能力。Linux 内核在 x86_64 架构下,通过复杂的机制实现了高效的内存管理,涵盖了虚拟内存、分页机制、内存分配、内存映射、内存保护、缓存管理等多个方面。本文将深入探讨这些机制,并结…...

JavaScript逆向高阶指南:突破基础,掌握核心逆向技术

JavaScript逆向高阶指南&#xff1a;突破基础&#xff0c;掌握核心逆向技术 JavaScript逆向工程是Web开发者和安全分析师的核心竞争力。无论是解析混淆代码、分析压缩脚本&#xff0c;还是逆向Web应用架构&#xff0c;掌握高阶逆向技术都将助您深入理解复杂JavaScript逻辑。本…...

嵌入式知识点总结 Linux驱动 (四)-中断-软硬中断-上下半部-中断响应

针对于嵌入式软件杂乱的知识点总结起来&#xff0c;提供给读者学习复习对下述内容的强化。 目录 1.硬中断&#xff0c;软中断是什么&#xff1f;有什么区别&#xff1f; 2.中断为什么要区分上半部和下半部&#xff1f; 3.中断下半部一般如何实现&#xff1f; 4.linux中断的…...

在ubuntu下一键安装 Open WebUI

该脚本用于自动化安装 Open WebUI&#xff0c;并支持以下功能&#xff1a; 可选跳过 Ollama 安装&#xff1a;通过 --no-ollama 参数跳过 Ollama 的安装。自动清理旧目录&#xff1a;如果安装目录 (~/open-webui) 已存在&#xff0c;脚本会自动删除旧目录并重新安装。完整的依…...

c语言网 1127 尼科彻斯定理

原题 题目描述 验证尼科彻斯定理&#xff0c;即&#xff1a;任何一个整数m的立方都可以写成m个连续奇数之和。 输入格式 任一正整数 输出格式 该数的立方分解为一串连续奇数的和 样例输入 13 样例输出 13*13*132197157159161163165167169171173175177179181 ​ #include<ios…...

Cloudflare通过代理服务器绕过 CORS 限制:原理、实现场景解析

第一部分&#xff1a;问题背景 1.1 错误现象复现 // 浏览器控制台报错示例 Access to fetch at https://chat.qwenlm.ai/api/v1/files/ from origin https://ocr.doublefenzhuan.me has been blocked by CORS policy: Response to preflight request doesnt pass access con…...

吴恩达深度学习——如何实现神经网络

来自吴恩达深度学习&#xff0c;仅为本人学习所用。 文章目录 神经网络的表示计算神经网络的输出激活函数tanh选择激活函数为什么需要非激活函数双层神经网络的梯度下降法 随机初始化 神经网络的表示 对于简单的Logistic回归&#xff0c;使用如下的计算图。 如果是多个神经元…...

《STL基础之vector、list、deque》

【vector、list、deque导读】vector、list、deque这三种序列式的容器&#xff0c;算是比较的基础容器&#xff0c;也是大家在日常开发中常用到的容器&#xff0c;因为底层用到的数据结构比较简单&#xff0c;笔者就将他们三者放到一起做下对比分析&#xff0c;介绍下基本用法&a…...

LockSupport概述、阻塞方法park、唤醒方法unpark(thread)、解决的痛点、带来的面试题

目录 ①. 什么是LockSupport? ②. 阻塞方法 ③. 唤醒方法(注意这个permit最多只能为1) ④. LockSupport它的解决的痛点 ⑤. LockSupport 面试题目 ①. 什么是LockSupport? ①. 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作 ②. LockSupport是一个线程阻塞…...

Android开发基础知识

1 什么是Android&#xff1f; Android&#xff08;读音&#xff1a;英&#xff1a;[ndrɔɪd]&#xff0c;美&#xff1a;[ˈnˌdrɔɪd]&#xff09;&#xff0c;常见的非官方中文名称为安卓&#xff0c;是一个基于Linux内核的开放源代码移动操作系统&#xff0c;由Google成立…...

C++ Lambda 表达式的本质及原理分析

目录 1.引言 2.Lambda 的本质 3.Lambda 的捕获机制的本质 4.捕获方式的实现与底层原理 5.默认捕获的实现原理 6.捕获 this 的机制 7.捕获的限制与注意事项 8.总结 1.引言 C 中的 Lambda 表达式是一种匿名函数&#xff0c;最早在 C11 引入&#xff0c;用于简化函数对象的…...

《多线程基础之条件变量》

【条件变量导读】条件变量是多线程中比较灵活而且容易出错的线程同步手段&#xff0c;比如&#xff1a;虚假唤醒、为啥条件变量要和互斥锁结合使用&#xff1f;windows和linux双平台下&#xff0c;初始化、等待条件变量的api一样吗&#xff1f; 本文将分别为您介绍条件变量在w…...

21款炫酷烟花合集

系列专栏 《Python趣味编程》《C/C趣味编程》《HTML趣味编程》《Java趣味编程》 写在前面 Python、C/C、HTML、Java等4种语言实现18款炫酷烟花的代码。 Python Python烟花① 完整代码&#xff1a;Python动漫烟花&#xff08;完整代码&#xff09; ​ Python烟花② 完整…...

智能风控 数据分析 groupby、apply、reset_index组合拳

目录 groupby——分组 本例 apply——对每个分组应用一个函数 等价用法 reset_index——重置索引 使用前​编辑 注意事项 groupby必须配合聚合函数、 关于agglist 一些groupby试验 1. groupby对象之后。sum&#xff08;一个列名&#xff09; 2. groupby对象…...

Python网络自动化运维---用户交互模块

文章目录 目录 文章目录 前言 实验环境准备 一.input函数 代码分段解析 二.getpass模块 前言 在前面的SSH模块章节中&#xff0c;我们都是将提供SSH服务的设备的账户/密码直接写入到python代码中&#xff0c;这样很容易导致账户/密码泄露&#xff0c;而使用Python中的用户交…...

【JVM】调优

目的&#xff1a; 减少minor gc、full gc的次数&#xff0c;也就是减少STW的时间&#xff0c;因为java虚拟机在做后台垃圾收集线程的时候&#xff0c;会停掉其他线程&#xff0c;专门做垃圾收集&#xff0c;这样会影响网站的性能&#xff0c;以及用户的体验。 调优位置&#x…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

srs linux

下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台

淘宝扭蛋机小程序系统的开发&#xff0c;旨在打造一个互动性强的购物平台&#xff0c;让用户在购物的同时&#xff0c;能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机&#xff0c;实现旋转、抽拉等动作&#xff0c;增…...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行&#xff1a; rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...