c++ std::variant用法

本文介绍了C++中的std::variant,一种可辨识类型安全的联合类型,解决了传统union的问题。文章详细讲解了std::variant的构造、使用、std::get和std::holds_alternative等关键功能,并讨论了std::monostate的作用和类型匹配问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

std::variant

Union类型的问题:

  • 无法知道当前使用的类型是什么
  • union无法自动调用底层数据成员的析构函数。联合体无法对其内部的数据属性的生命周期的全面支持,因为当外部代码调用Union时在切换类型,它无法做到对当前使用的对象,并自动调用其析构函数。
  • C/C++没有原生的工具可以检测Union内部当前的活动类型。
  • 不能有non-trivial的成员,比如std::string(从c++ 11起, union原则上可以有non-trivial的成员,但是必须实现特殊的成员函数,比如复制构造函数和析构函数,因为只有通过代码逻辑才能知道哪个成员是可用的。)
  • 不能从union中派生类。

在 C++17 之前,为了改进这些问题,提出了std::variant。

  • 与C语言中传统的 union 类型相同的是,variant 也是联合(union)类型。即 variant 可以存放多种类型的数据,但任何时刻最多只能存放其中一种类型的数据。
  • 与C语言中传统的 union 类型所不同的是,variant 是可辨识的类型安全的联合(union)类型。即 variant 无须借助外力只需要通过查询自身就可辨别实际所存放数据的类型。

std::variant 基础用法

构造函数

std::variant<int, double, std::string> x, y;

x,y是一个可存放 int, double, std::string 这三种类型数据的变体类型的对象

std::in_place_index、std::in_place_type 显式赋值

显式指定当前索引/类型,并使用后续参数进行原地构造

variant<vector<int>, string> v{ std::in_place_index<0>, { 0, 1, 2, 3 } };
variant<vector<int>, string> v{ std::in_place_type<int>, { 0, 1, 2, 3 } };

修改值

赋值和emplace()操作对应于初始化


std::variant<int, int, std::string> var; // sets first int to 0, index()==0
var = "hello"; // sets string, index()==2
var.emplace<1>(42); // sets second int, index()==1

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

x = 100.0f;
std::cout << "可变体的活动类型返回的index:" << x.index() << std::endl;

std::monostate

只是一个空类型,被用来和variant一起使用来表示空状态。当variant中的第一个类型没有默认构造函数时,可以将std::monostate作为第一个类型来使用。也就是说,std::monostate可以作为第一种替代类型,使变体类型默认为可构造的。

#include <iostream>
#include <variant>
 
struct NoDefConstr
{
    NoDefConstr(int i)
    {
        std::cout << "NoDefConstr::NoDefConstr(int) called\n";
    }
};
 
int main()
{
    std::variant<std::monostate, NoDefConstr> v2; // OK
    std::cout << "index: " << v2.index() << '\n'; // prints 0
 
    if (v2.index() == 0) 
    {
        std::cout << "has monostate\n";
    }
    if (!v2.index()) 
    {
        std::cout << "has monostate\n";
    }
    if (std::holds_alternative<std::monostate>(v2)) 
    {
        std::cout << "has monostate\n";
    }
    if (std::get_if<0>(&v2)) 
    {
        std::cout << "has monostate\n";
    }
    if (std::get_if<std::monostate>(&v2)) 
    {
        std::cout << "has monostate\n";
    }
 
    return 0;
}

获取std::variant中的值

使用std::get() 或直接std::get()来获取variant中包含的值,std::get(v) 如果变体类型 v 存放的数据类型为 T,那么返回所存放的数据,否则报错

double d = std::get<double>(x);
std::string s = std::get<2>(y);

如果std::variant中当前存储的不是对应Type的值, 则会抛出std::bad_variant_access类型的异常

try
{
    int i = std::get<int>(x);
}
catch (std::bad_variant_access e)
{
    std::cerr << e.what() << std::endl;
}

get_if()

除了会引发异常的std::get<>,也有无异常的 std::get_if() 方法,get_if通常保证std::get在访问可变体时不会抛出bad_variant_access 异常,提供了访问前的类型安全判断。需要自行判断返回的指针类型是否为空。
std::get_if(&v) 如果变体类型 v 存放的数据类型为 T,那么返回所存放数据的指针,否则返回空指针。

int* i = std::get_if<int>(&x);
if (i == nullptr)
{
    std::cout << "wrong type" << std::endl;
}
else
{
    std::cout << "value is " << *i << std::endl;
}

同时具有bool和std::string

如果一个std::variant<>同时有bool和std::string两个备选项,字符串字面量转换为bool比转换为std::string匹配

 #include <iostream>
#include <variant>
 
int main()
{
    std::variant<bool, std::string> v;
    v = "hi"; // OOPS: sets the bool alternative
    std::cout << "index: " << v.index() << '\n';
 
    std::visit([](const auto& val) {std::cout << "value: " << val << '\n'; }, v);
 
    return 0;
}

在这里插入图片描述
字符串常量值被解释为通过Boolean值true初始化变量(true是因为指针不是0)

解决方案

v.emplace<1>("hello"); // explicitly assign to second alternative
v.emplace<std::string>("hello"); // explicitly assign to string alternative
v = std::string{"hello"}; // make sure a string is assigned
 
using namespace std::literals; // make sure a string is assigned
v = "hello"s;

std::holds_alternative(v)

查询变体类型 v 是否存放了 T 类型的数据。

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

using namespace std;

int main()
{
    variant<int, double, string> v; // v == 0
    v = 1;
    bool has_int = holds_alternative<int>(v);
    bool has_double = holds_alternative<double>(v);
    cout << v.index() << has_int << has_double << get<0>(v) << *get_if<0>(&v) << endl; // 01011
    v = 2.0;
    cout << v.index() << (get_if<int>(&v) == nullptr) << get<1>(v) << get<double>(v) << endl; // 1122
    v = "a";
    cout << v.index() << get<2>(v) << get<string>(v) << endl; // 2aa
}
### C++ 中 `std::variant` 的用法及其常见错误解决方案 #### 什么是 `std::variant` `std::variant` 是一种类型安全的联合体(union),它允许存储多种类型的值之一。它是自 C++17 起引入的标准库组件,提供了更灵活的方式处理多态数据结构[^3]。 以下是其主要特性: - 它可以保存一组预定义类型中的任意一个。 - 当前活动的类型可以通过访问器函数获取。 - 如果尝试访问未激活的类型,则会抛出异常 `std::bad_variant_access`。 #### 基本语法与示例 下面是一个简单的例子展示如何声明和使用 `std::variant`: ```cpp #include <iostream> #include <variant> int main() { std::variant<int, double, std::string> v; // 设置 int 类型 v = 42; if (std::holds_alternative<int>(v)) { std::cout << "Value is an integer: " << std::get<int>(v) << '\n'; } // 设置 double 类型 v = 3.14; if (std::holds_alternative<double>(v)) { std::cout << "Value is a double: " << std::get<double>(v) << '\n'; } // 设置 string 类型 v = "Hello"; if (std::holds_alternative<std::string>(v)) { std::cout << "Value is a string: " << std::get<std::string>(v) << '\n'; } } ``` 上述代码展示了如何通过 `std::get<T>` 获取特定类型的值以及如何利用 `std::holds_alternative<T>` 来判断当前存储的是哪种类型[^3]。 #### 访问控制机制 为了简化不同类型的访问操作,标准库还提供了一个辅助工具——`std::visit` 函数模板。它可以接受一个可调用对象作为参数,并将其应用于 `std::variant` 所持有的具体类型上。 以下是如何结合 `std::visit` 使用的例子: ```cpp #include <iostream> #include <variant> #include <string> struct PrintVisitor { template<typename T> void operator()(T&& arg) const { std::cout << arg << "\n"; } }; int main() { std::variant<int, float, std::string> var = "Example"; std::visit(PrintVisitor{}, var); // 输出 Example } ``` 这里我们创建了一个通用访客类来打印任何可能被存入 variant 的类型的数据[^4]。 #### 错误案例分析及解决方法 当使用 `std::variant` 时可能会遇到一些常见的问题,比如试图访问不存在的类型或者忘记初始化变量等。这些问题通常会导致运行期错误甚至程序崩溃。 ##### 案例一:非法访问引发异常 如果直接调用了不匹配类型的 getter 方法而没有先验证该类型是否存在的话,就会触发 `std::bad_variant_access` 异常。 **修复建议**: 总是在访问之前确认所需类型确实存在再继续下一步动作。 ```cpp try { auto value = std::get<float>(var); } catch(const std::bad_variant_access& e){ std::cerr << "Error accessing wrong type." << std::endl; } ``` ##### 案例二:默认构造行为不明朗 某些情况下,默认构造出来的 variant 可能处于不确定状态,这取决于编译器实现细节。 **最佳实践**: 明确指定初始值或确保所有潜在选项都有合理缺省设置。 ```cpp // 正确做法 std::variant<int, std::string> safeVar{0}; // 避免这种模糊情况 std::variant<int, std::string> unsafeVar; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值