系统性掌握C++17容器四件套:std::optional, std::any, std::variant, std::tuple

news2024/11/25 18:35:06

昨天在写《深入探讨C++的高级反射机制(2):写个能用的反射库》的时候,正好遇到动态反射需要的类型擦除技术。所谓的类型擦除,就是在两个模块之间的接口层没有任何类型信息,实现两个模块之间安全的通信。可以理解为:
在这里插入图片描述
为了实现这个功能,于是用到了std::any这个工具。考虑到许多开发者分不清std::variant和std::any之间的区别,于是萌发了写一篇文章系统性介绍一下他们的想法。

传统上,C++17标准为C++的类型系统和容器库带来了重要的补充和改进,std::optional, std::any, 和 std::variant这三种类型被统称为“C++17容器三剑客”,本文将在C++11引入在C++17获得的增强的std::tuple也纳入介绍,系统性地揭示C++体系中这类容器的完整面貌。

1. std::optional —— 语义明确的可选值

std::optional是一个模板类型,它提供了一种表示“可能没有值”的方式。在C++17之前,程序员通常会使用指针、特殊值或者布尔标记来表达这种“可选”语义,这些方法都有其局限性和缺陷。std::optional的出现,让这种表达方式变得更加安全和直观。

1.1 std::optional的基本概念
std::optional可以看作是一个可能包含类型T的值的容器。它提供了一种检查是否存储了值的安全方式,并且可以用简洁的API访问该值或者处理值不存在的情况。

1.2 使用场景

  • 函数可能无法返回有效值时
  • 配置项可能未设置时
  • 缓存结果可能不存在时

1.3 基本用法

#include <optional>
#include <iostream>

std::optional<int> maybeGetInt(bool flag) {
    if (flag) {
        return 123; // 返回有效的int
    }
    return {}; // 返回一个空的optional
}

int main() {
    auto val = maybeGetInt(true);
    if (val) { // 检查是否含有值
        std::cout << "Value: " << *val << std::endl; // 解引用访问值
    }

    auto noVal = maybeGetInt(false);
    std::cout << "No value: " << noVal.value_or(-1) << std::endl; // 使用value_or提供默认值

    return 0;
}

2. std::any —— 类型安全的void*

对于需要存储任意类型的值,C++17提供了std::any。这个容器可以存储任何类型的单个值,并且能够在运行时安全地访问存储的值。这对于编写泛型代码或者需要类型擦除的场景非常有用。非常类似C语义中的void*,不过差别是void*本身不能直接存储对象,而std::any本身提供了存储对象的能力。当然,也可以用std::any存储指针类型。

2.1 std::any的基本概念
std::any可以存储任意类型的值,只要该类型是可复制的。使用std::any_cast可以试图取回原始类型的值,如果类型不匹配,会抛出std::bad_any_cast异常。

2.2 使用场景

  • 动态类型的API设计
  • 类型安全的容器
  • 简化类型擦除实现

2.3 基本用法

#include <any>
#include <iostream>

int main() {
    std::any a = 10;
    std::cout << std::any_cast<int>(a) << std::endl; // 正确类型转换

    a = std::string("Hello, std::any!");
    std::cout << std::any_cast<std::string>(a) << std::endl; // 正确类型转换

    try {
        std::cout << std::any_cast<float>(a) << std::endl; // 错误类型转换,将抛出异常
    } catch (const std::bad_any_cast& e) {
        std::cout << e.what() << std::endl;
    }
	// 你可以使用不抛异常的指针版本的转换:
	auto casted_a = std::any_cast<float*>(a); // 错误类型转换,但不会抛异常,返回空指针
    return 0;
}

2.4 实现原理
std::any 的本质就是一段内存,内存承载的对象是“CopyConstructible”。在MSVC的STL中,如果是平凡对象,则直接存储,否则如果对象地址小于48字节,那么就会直接存储在std::any成员数组中,如果都不满足,就会通过malloc申请对于内存进行存储。
在这里插入图片描述
std::any在还原类型时,核心逻辑如下:
在这里插入图片描述
这段代码笔者依次注释如下:

template <class _Decayed>
_NODISCARD const _Decayed* _Cast() const noexcept {
    const type_info* const _Info = _TypeInfo();
      
    // 首先获取 std::any 对象当前存储值的类型信息。如果没有存储任何值(_Info 为空),或者存储的值的类型不是 _Decayed 类型,那么函数会返回 nullptr。
    if (!_Info || *_Info != typeid(_Decayed)) {
        return nullptr;
    }
    
    // 判断 _Decayed 类型是否为简单类型(POD 类型,可以直接复制内存)
    if constexpr (_Any_is_trivial<_Decayed>) {
        // 获取指向存储的 _Decayed 类型的平凡(trivial)值的指针
        return reinterpret_cast<const _Decayed*>(&_Storage._TrivialData);
    } 
    // 判断 _Decayed 类型是否足够小,可以存储在 std::any 的小对象优化缓冲区中
    else if constexpr (_Any_is_small<_Decayed>) {
        // 获取指向存储的 _Decayed 类型的小对象(small object)值的指针
        return reinterpret_cast<const _Decayed*>(&_Storage._SmallStorage._Data);
    } 
    // 大对象情况,即 _Decayed 类型的对象无法放入小对象优化缓冲区中,需要动态分配内存
    else {
        // 获取指向存储的 _Decayed 类型的大对象(big object)值的指针
        return static_cast<const _Decayed*>(_Storage._BigStorage._Ptr);
    }
}


3. std::variant —— 安全的联合体

std::variant是一个类型安全的联合体。它可以存储定义在它的模板参数列表中的任意类型的值。与C联合体不同的是,std::variant总是知道它当前存储的是哪种类型的值。

3.1 std::variant的基本概念
std::variant<…>可以被理解为一个可以存储多种类型中的一种的容器。使用std::get或std::get_if可以安全地访问存储的值。如果访问的类型不是当前存储的类型,会抛出std::bad_variant_access异常。

3.2 使用场景

  • 需要在同一位置存储不同类型值的情况
  • 替代传统的union或void*指针
  • 类型安全的状态机实现

3.3 基本用法

#include <variant>
#include <iostream>
#include <string>

int main() {
    std::variant<int, std::string> v = 20;
    std::cout << std::get<int>(v) << std::endl; // 正确类型访问

    v = "Variant can hold a string now!";
    std::cout << std::get<std::string>(v) << std::endl; // 正确类型访问

    try {
        std::cout << std::get<double>(v) << std::endl; // 错误类型访问,将抛出异常
    } catch (const std::bad_variant_access& e) {
        std::cout << e.what() << std::endl;
    }

    return 0;
}

4. std::tuple—— 异构元素的组合器

虽然std::tuple并非C++17的新特性,它自C++11起就已经存在,但它在C++17获得更好的完善和加强,并且与std::variant有着互补的特性,在处理类型异构的数据结构时非常有用。

std::tuple允许我们将任意数量和类型的元素组合成单一对象。与std::variant相比,std::tuple可以存储多个不同类型的值,同时保持每个值的类型信息。这使得std::tuple成为了执行多任务返回值、聚合不同类型数据以及实现类型相关算法的理想选择。

4.1 std::tuple的基本概念
std::tuple<T1, T2, ..., TN>可以看作是一个异构的固定大小容器,它可以包含任意数目(N)的不同类型(T1, T2, …, TN)的元素。std::tuple对于打包数据和从函数返回多个值非常有用。

4.2 使用场景

  • 函数需要返回多个值时
  • 将一组不同类型的数据作为单个单位处理时
  • 用于实现编译时计算和元编程技术

4.3 基本用法

#include <tuple>
#include <string>
#include <iostream>

std::tuple<int, std::string, float> createComplexObject() {
    return std::make_tuple(42, "Test", 3.14f);
}

int main() {
    auto [id, name, value] = createComplexObject(); // 结构化绑定(C++17特性)

    std::cout << "ID: " << id << std::endl;
    std::cout << "Name: " << name << std::endl;
    std::cout << "Value: " << value << std::endl;

    // 也可以使用std::get访问tuple中的元素
    std::tuple<int, double, std::string> t = std::make_tuple(1, 2.0, "tuple");
    std::cout << "First element: " << std::get<0>(t) << std::endl;
    std::cout << "Second element: " << std::get<1>(t) << std::endl;

    return 0;
}

总结:

  1. std::optional:常常用于代替nullptr实现空安全(用来包装非指针类型)或其他可能为空的场景。
  2. std::any:STL中少有的不需要指定容器内容类型的模板类(但是在使用时需要传入容器内容类型以获取内容),常用于类型擦除。相当于C++版本的void*。
  3. std::variant:C++版本的“联合体”。提供了类型安全的联合功能。
  4. std::tuple:std::pair 的增强版,支持任意数量的异构元素存储。

如果你的编译器不支持C++17,那么可以了解Boost 库提供的 Boost.Variant 和 Boost.Any 类型。它们提供了类似的功能,在旧代码或不支持 C++17 的环境中,可以考虑采用作为代替。

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

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

相关文章

QT控制comboBox切换方法

目录 1. 效果2. 操作 1. 效果 如下图&#xff1a; 点击全切换雨天模式按钮 则 comboBox 文本显示为 “雨天模式”点击全切换正常模式按钮 则 comboBox 文本显示为 “雨天模式” 切换到 雨天模式 切换到 正常模式 2. 操作 使用 “setCurrentIndex” 方法&#xff0c;切换 combo…

# bash: chkconfig: command not found 解决方法

bash: chkconfig: command not found 解决方法 一、chkconfig 错误描述&#xff1a; 这个错误表明在 Bash 环境下&#xff0c;尝试执行 chkconfig 命令&#xff0c;但是系统找不到这个命令。chkconfig 命令是一个用于管理 Linux 系统中服务的启动和停止的工具&#xff0c;通常…

[数据集][目标检测]电力场景下电柜箱门把手检测数据集VOC+YOLO格式1167张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1167 标注数量(xml文件个数)&#xff1a;1167 标注数量(txt文件个数)&#xff1a;1167 标注…

26、 MySQL数据库基础练习系列股票交易系统基础查询和复杂查询

5、基础查询 -- 1、查询用户信息仅显示姓名与手机号 SELECT username as 姓名,phone as 手机号 from users;-- 2、模糊查询和explain语句 alter table stocks add index stock_name_index(stock_name); explain SELECT * from stocks where stock_name like %东吴证券%; -- 3、…

Attention步骤

一个典型的Attention思想包括三部分&#xff1a;Qquery、Kkey、Vvalue。 Q是query&#xff0c;是输入的信息&#xff1b;key和value成组出现&#xff0c;通常是原始文本等已有的信息&#xff1b;通过计算Q与K之间的相关性a&#xff0c;得出不同的K对输出的重要程度&#xff1b;…

2024年北京市安全员-C3证证模拟考试题库及北京市安全员-C3证理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年北京市安全员-C3证证模拟考试题库及北京市安全员-C3证理论考试试题是由安全生产模拟考试一点通提供&#xff0c;北京市安全员-C3证证模拟考试题库是根据北京市安全员-C3证最新版教材&#xff0c;北京市安全员-C…

【深度学习】机器学习基础

机器学习就是让机器具备找一个函数的能力 带有未知的参数的函数称为模型 通常一个模型的修改&#xff0c;往往来自于对这个问题的理解&#xff0c;即领域知识。 损失函数 平均绝对误差&#xff08;Mean Absolute Error&#xff0c;MAE&#xff09; 均方误差&#xff08;Mea…

送物机器人电子方案定制

这是一款集娱乐、教育和互动于一身的高科技产品。 一、它的主要功能包括&#xff1a; 1. 智能对话&#xff1a;机器人可以进行简单的对话&#xff0c;回答用户的问题&#xff0c;提供有趣的互动体验。 2. 前进、后退、左转、右转、滑行&#xff1a;机器人可以通过遥控器或AP…

秋招突击——6/26~6/27——复习{二维背包问题——宠物小精灵之收服}——新作{串联所有单词的字串}

文章目录 引言复习二维背包问题——宠物小精灵之收服个人实现重大问题 滚动数组优化实现 新作串联所有单词的字串个人实现参考实现 总结 引言 今天应该是舟车劳顿的一天&#xff0c;头一次在机场刷题&#xff0c;不学习新的东西了&#xff0c;就复习一些之前学习的算法了。 复…

SherlockChain:基于高级AI实现的智能合约安全分析框架

关于SherlockChain SherlockChain是一款功能强大的智能合约安全分析框架&#xff0c;该工具整合了Slither工具&#xff08;一款针对智能合约的安全工具&#xff09;的功能&#xff0c;并引入了高级人工智能模型&#xff0c;旨在辅助广大研究人员针对Solidity、Vyper和Plutus智…

CentOS安装ntp时间同步服务

CentOS安装ntp时间同步服务 安装ntp 检查服务器是否安装ntp&#xff1a; rpm -q ntp安装ntp&#xff1a; yum install -y ntp服务端配置 配置文件路径&#xff1a;/etc/ntp.conf 设置ntp为开机启动 systemctl enable ntpd查看ntp开机启动状态 enabled:开启, disabled:关闭 …

国际产业园双创孵化空间点亮创业梦想

国际数字影像产业园的双创孵化空间旨在打造一个集创意、技术、资金、市场等资源于一体的综合孵化平台&#xff0c;为初创企业和创新项目提供从创意到产品化、从初创到成长的全方位支持。 主要功能与服务 1、孵化服务&#xff1a;为初创企业提供办公场地、基础设施、技术支持等…

navicat Premium发布lite免费版本了

Navicat Premium发布lite免费版本了&#xff0c;下面是完整功能对比链接 Navicat Premium 功能列表 | Navicat 免费版本下载链接如下&#xff1a; Navicat | 免费下载 Navicat Premium Lite 开发功能完全够用&#xff0c;点赞。 dbeaver该如何应对。

幻兽帕鲁Palworld樱花版本服务器一键开服联机

1、登录服务器&#xff08;百度莱卡云&#xff09; 1.1、第一次购买服务器会安装游戏端&#xff0c;大约5分钟左右&#xff0c;如果长时间处于安装状态请联系客服 2、在启动中调整游戏参数 2.1、重启服务器&#xff0c;等待running出现&#xff0c;或者运行时间变为灰色&#x…

MySQL高级-索引-使用规则-前缀索引

文章目录 1、前缀索引2、前缀长度3、查询表数据4、查询表的记录总数5、计算并返回具有电子邮件地址&#xff08;email&#xff09;的用户的数量6、从tb_user表中计算并返回具有不同电子邮件地址的用户的数量7、计算唯一电子邮件地址&#xff08;email&#xff09;的比例相对于表…

为什么要本地化您的多媒体内容?

当我们访问网站、应用程序和社交媒体时&#xff0c;体验不再局限于陈旧的文本和静态图像。现代处理能力和连接速度提高了快速加载视频、音频和动画的可能性。 这一切都提供了更具沉浸感和互动性的用户体验。多媒体是数字营销中最有效的内容之一&#xff0c;因为它对用户更具吸…

优思学院|工厂的部门架构管理与精益生产

工厂内有不同部门&#xff0c;各部门之间必须协调合作才能发挥整体功能。工厂最主要的部分是制造产品的现场&#xff0c;这里安装了生产工具&#xff0c;还有操作员进行加工或生产制造。 制造时使用的材料或零组件&#xff0c;需要对外采购。对于加工组装型的工厂&#xff0c;…

单片机+DS18B20温度控制程序仿真与原理图PCB文件 可设上下限

资料下载地址&#xff1a;单片机DS18B20温度控制程序仿真与原理图PCB文件 可设上下限 目录 1、项目介绍 2、实物图 ​3、电路原理图 ​4、仿真原理图 ​5、部分代码 1、项目介绍 基于51单片机温度控制&#xff0c;使用18b20来做温度传感器&#xff0c;四位共阳数码管显…

python中类的继承详解

面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力&#xff1a;它可以使用现有类的所有功能&#xff0c;并在无需重新编写原来的类的情况下对这些功能进行扩展 &#xff08;1&#xff09;在类的继承中&#xff0c;存在父类跟子类&#xff0c;子类可以继…

【pytorch09】数学运算

1.数学操作 add/minus/multiply/dividematmulpowsqrt/rsqrtround 2.加减乘除 加法 矩阵乘法 torch.mm 只适用于2d torch.matmul 要分清楚是矩阵元素相乘&#xff0c;还是矩阵相乘 例子 x一共有4张照片&#xff0c;每张照片打平成784的向量&#xff0c;希望降维得到[4,51…