现代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 等强大工具,使得代码更加简洁和易读。

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

相关推荐
阿福的工作室1 分钟前
react跳转传参的方法
前端·react.js·前端框架
远洋录2 分钟前
大型前端应用状态管理实战:从 Redux 到 React Query 的演进之路
前端·人工智能·react
liuweni4 分钟前
Next.js系统性教学:深入理解缓存交互与API缓存管理
开发语言·前端·javascript·经验分享·缓存·前端框架·交互
m0_7482517211 分钟前
Spring Boot 经典九设计模式全览
java·spring boot·设计模式
潘多编程12 分钟前
Spring Boot性能提升:实战案例分析
java·spring boot·后端
m0_7482561412 分钟前
Spring Boot 整合 Keycloak
java·spring boot·后端
m0_7482402513 分钟前
前端编程艺术(5)---Vue3(从零基础到项目开发)
前端
#HakunaMatata13 分钟前
Java 中 List 接口的学习笔记
java·学习·list
Ase5gqe19 分钟前
Spring Boot中实现JPA多数据源配置指南
java
AI人H哥会Java20 分钟前
【JAVA】Java高级:多数据源管理与Sharding:在Spring Boot应用中实现多数据源的管理
java·开发语言