现代C++20 variant

文章目录

      • [1. **概述**](#1. 概述)
      • [2. **成员函数**](#2. 成员函数)
        • [2.1 **构造函数和析构函数**](#2.1 构造函数和析构函数)
        • [2.2 **赋值操作符**](#2.2 赋值操作符)
        • [2.3 **观察者函数**](#2.3 观察者函数)
        • [2.4 **访问函数**](#2.4 访问函数)
        • [2.5 **单子操作(自 C++26 起)**](#2.5 单子操作(自 C++26 起))
      • [3. **非成员函数**](#3. 非成员函数)
      • [4. **辅助类和常量**](#4. 辅助类和常量)
      • [5. **示例代码**](#5. 示例代码)
        • [示例 1:基本用法](#示例 1:基本用法)
        • 输出:
        • [示例 2:使用 `std::visit` 访问 `variant`](#示例 2:使用 std::visit 访问 variant)
        • 输出:
        • [示例 3:使用 `std::monostate` 作为默认值](#示例 3:使用 std::monostate 作为默认值)
        • 输出:
        • [示例 4:使用 `std::get_if` 检查并访问值](#示例 4:使用 std::get_if 检查并访问值)
        • 输出:
      • [6. **总结**](#6. 总结)

std::variant 是 C++17 引入的一个类模板,用于表示类型安全的联合体(union)。与传统的 C 风格联合体不同, std::variant 提供了更强的类型安全性和更丰富的功能。它可以在任何给定时间包含多个备用类型之一的值,并且可以处理这些类型的转换和访问。

1. 概述

std::variant 的定义如下:

cpp 复制代码
template<class... Types>
class variant;
  • Types... :一个或多个类型,表示 variant 可以存储的备用类型。所有类型都必须满足可销毁的要求,不能是数组、引用或 void 类型。

std::variant 在任何给定时间点要么包含其备用类型之一的值,要么在错误的情况下不包含任何值(这种情况很少见,通常发生在异常抛出时)。std::variant 不会分配额外的动态内存,所有对象都嵌套在 variant 对象本身中。

2. 成员函数

2.1 构造函数和析构函数
  • 默认构造函数 :如果第一个备用类型是可默认构造的,则创建一个包含该类型的 variant;否则,variant 不可默认构造。
  • 带参数的构造函数 :接受一个备用类型的值,初始化 variant 以包含该值。
  • in_place 构造 :就地构造 variant 中的值,避免不必要的拷贝或移动。
  • 析构函数 :销毁 variant 对象及其包含的值。
2.2 赋值操作符
  • operator= :将一个 variant 的内容赋值给另一个 variant,或者将一个备用类型的值赋值给 variant
2.3 观察者函数
  • index() :返回 variant 当前所包含的备用类型的零基索引。如果 variant 处于无效状态(即 valueless_by_exception),则返回 variant_npos
  • valueless_by_exception() :检查 variant 是否处于无效状态,即由于异常抛出而不包含任何值。
2.4 访问函数
  • std::get<T>(variant)std::get<I>(variant) :根据类型 T 或索引 I 获取 variant 中的值。如果 variant 不包含该类型或索引的值,抛出 std::bad_variant_access 异常。
  • std::get_if<T>(&variant)std::get_if<I>(&variant) :获取指向 variant 中值的指针,根据类型 T 或索引 I。如果 variant 不包含该类型或索引的值,返回 nullptr
  • holds_alternative<T>(variant) :检查 variant 是否当前包含给定类型 T
2.5 单子操作(自 C++26 起)
  • visit :使用 variant 所包含的参数调用提供的函数对象。visitstd::variant 的成员函数,允许直接对 variant 进行访问。

3. 非成员函数

  • std::visit :使用一个或多个 variant 所包含的参数调用提供的函数对象。std::visit 是一个非常强大的工具,用于处理 variant 中的不同类型。
  • std::swap :专门化 std::swap 算法,用于交换两个 variant 的内容。
  • 比较运算符std::variant 支持按字典顺序进行比较。C++20 引入了三路比较运算符 operator<=>,它可以在支持三路比较的类型上使用。

4. 辅助类和常量

  • std::monostate :占位符类型,用作不可默认构造的类型的 variant 中的第一个备选方案。std::monostate 允许 variant 可以默认构造。
  • std::bad_variant_access :当尝试访问 variant 中不存在的值时抛出的异常。
  • std::variant_sizestd::variant_size_v :在编译时获取 variant 的备选方案列表的大小。
  • std::variant_alternativestd::variant_alternative_t:在编译时获取由索引指定的备选方案的类型。
  • std::hash<std::variant> :对 std::variant 的哈希支持。
  • std::variant_nposvariant 在无效状态下的索引。

5. 示例代码

示例 1:基本用法
cpp 复制代码
#include <iostream>
#include <variant>
#include <string>

int main() {
    // 定义一个 variant,可以存储 int 或 float
    std::variant<int, float> v;

    // 初始化为 int
    v = 42;
    std::cout << "v contains: " << std::get<int>(v) << '\n';

    // 修改为 float
    v = 3.14f;
    std::cout << "v contains: " << std::get<float>(v) << '\n';

    // 尝试访问不存在的类型,会抛出异常
    try {
        std::get<std::string>(v);
    } catch (const std::bad_variant_access& e) {
        std::cout << "Error: " << e.what() << '\n';
    }

    return 0;
}
输出:
复制代码
v contains: 42
v contains: 3.14
Error: std::bad_variant_access: Attempted to access an inactive value in a variant.
示例 2:使用 std::visit 访问 variant
cpp 复制代码
#include <iostream>
#include <variant>
#include <string>

// 定义一个 visitor 结构体,用于处理不同的类型
struct PrintVisitor {
    void operator()(int i) const {
        std::cout << "Integer: " << i << '\n';
    }
    void operator()(float f) const {
        std::cout << "Float: " << f << '\n';
    }
    void operator()(const std::string& s) const {
        std::cout << "String: " << s << '\n';
    }
};

int main() {
    // 定义一个 variant,可以存储 int、float 或 string
    std::variant<int, float, std::string> v;

    // 初始化为 int
    v = 42;
    std::visit(PrintVisitor{}, v);

    // 修改为 float
    v = 3.14f;
    std::visit(PrintVisitor{}, v);

    // 修改为 string
    v = "Hello, World!";
    std::visit(PrintVisitor{}, v);

    return 0;
}
输出:
复制代码
Integer: 42
Float: 3.14
String: Hello, World!
示例 3:使用 std::monostate 作为默认值
cpp 复制代码
#include <iostream>
#include <variant>
#include <string>

int main() {
    // 定义一个 variant,可以存储 int、float 或 monostate
    std::variant<int, float, std::monostate> v;

    // 默认构造,包含 monostate
    std::cout << "v contains: " << v.index() << '\n'; // 输出 2,因为 monostate 是第三个类型

    // 修改为 int
    v = 42;
    std::cout << "v contains: " << std::get<int>(v) << '\n';

    // 修改为 float
    v = 3.14f;
    std::cout << "v contains: " << std::get<float>(v) << '\n';

    // 修改回 monostate
    v = std::monostate{};
    std::cout << "v contains: " << v.index() << '\n'; // 输出 2

    return 0;
}
输出:
复制代码
v contains: 2
v contains: 42
v contains: 3.14
v contains: 2
示例 4:使用 std::get_if 检查并访问值
cpp 复制代码
#include <iostream>
#include <variant>
#include <string>

int main() {
    // 定义一个 variant,可以存储 int 或 float
    std::variant<int, float> v;

    // 初始化为 int
    v = 42;

    // 使用 get_if 检查并访问 int
    if (auto* p = std::get_if<int>(&v)) {
        std::cout << "v contains int: " << *p << '\n';
    } else {
        std::cout << "v does not contain int\n";
    }

    // 修改为 float
    v = 3.14f;

    // 使用 get_if 检查并访问 float
    if (auto* p = std::get_if<float>(&v)) {
        std::cout << "v contains float: " << *p << '\n';
    } else {
        std::cout << "v does not contain float\n";
    }

    return 0;
}
输出:
复制代码
v contains int: 42
v contains float: 3.14

6. 总结

std::variant 是 C++17 引入的一个非常有用的工具,特别适用于需要在多个不同类型之间切换的场景。它提供了类型安全的联合体功能,并且通过 std::visitstd::get 等函数,使得访问和操作 variant 中的值变得非常方便。

std::variant 的主要优点包括:

  • 类型安全:避免了传统 C 风格联合体中的类型混淆问题。
  • 灵活性:可以存储多个不同类型,并且可以处理这些类型的转换和访问。
  • 性能高效 :所有对象都嵌套在 variant 对象本身中,不会分配额外的动态内存。
  • 丰富的功能 :提供了 std::visitstd::getstd::get_if 等强大工具,使得代码更加简洁和易读。

如果你有更多具体的问题或需要进一步的帮助,请随时提问!

相关推荐
phltxy17 小时前
Vue 核心特性实战指南:指令、样式绑定、计算属性与侦听器
前端·javascript·vue.js
tb_first18 小时前
LangChain4j简单入门
java·spring boot·langchain4j
独自破碎E18 小时前
【BISHI9】田忌赛马
android·java·开发语言
范纹杉想快点毕业18 小时前
实战级ZYNQ中断状态机FIFO设计
java·开发语言·驱动开发·设计模式·架构·mfc
Byron070718 小时前
Vue 中使用 Tiptap 富文本编辑器的完整指南
前端·javascript·vue.js
smileNicky18 小时前
布隆过滤器怎么提高误差率
java
css趣多多19 小时前
地图快速上手
前端
それども19 小时前
分库分表的事务问题 - 怎么实现事务
java·数据库·mysql
zhengfei61119 小时前
面向攻击性安全专业人员的一体化浏览器扩展程序[特殊字符]
前端·chrome·safari
Java面试题总结19 小时前
基于 Java 的 PDF 文本水印实现方案(iText7 示例)
java·python·pdf