std::variant特性详解

基础概念

std::variant与c++17版本引入,这是一个类型安全的联合体(union),可以存储多个不同类型中的任意一个。简单理解可以把std::variant认为是一个联合体(union)。在c++17引入这个特性之前,类似的功能只能使用联合体union来实现,但是联合体存在一些弊端:

  • 联合体无法知道当前存储的是哪个类型
  • union不会调用构造函数和析构函数
  • 可能导致未定义的行为

c++针对联合体的这些弊端推出了新的特性std::variant,通过这个特性可以创建一个对象,这个对象完全具备原有的联合体(union)的功能,并且解决了联合体存在的弊端。总结如下:

  1. std::variant是一个类模板,通过这个类模板可以创建变量,这个变量具有与联合体一样的功能
  2. std::variant对象的长度相对于联合体而言会稍长一些,因为除了包含最长对象的长度外,对象中还包含了一些控制信息
  3. std::variant支持自动调用类的构造函数与析构函数
  4. 虽然有了一些不同,但是使用方法与联合体基本一致,只是对比联合体功能更强大了
  5. 由于std::variant是一个类,其中包含了一些常用方法来访问这个对象中的相关信息
  6. 与联合体一样,同一时间std::variant仅存储一个数据类型的数据。

依赖的头文件:#include <variant>

基本使用方法

下面展示一些std::variant的基本使用方法:

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

int main()
{
    //创建一个可以存储int string double的变量 
    std::variant<int, std::string, double> data;
    
    data = 42;  //现在存储的是整型
    data = "hello";  //现在存储的是字符串类型
    data = 3.14;  //现在存储的是double

    //访问存储的数据的方法
    //方法一,通过std::get访问
    try
    {
        double d = std::get<double>(data); //正确,data最后存储的是double
        std::cout <<"value = <<d<<std::endl;
        
//此处会在运行时抛出异常,因为最后的存储的是double,不是int,类型不正确无法获取,这点与联合体有区别
        int i = std::get<int>(data); 
    }
    catch(const std::bad_variant_access& e)
    {
        std::cout <<"错误类型访问:"<<e.what()<<std::endl;
    }
    

    //方法二,使用std::get_if方法,这个方法返回的是指针
    if(auto pval = std::get_if<double>(&data))
    {
        std::cout<<"value = "<<*pval<<std::endl;  
    }
    //若没有知道则返回空指针
    else
    {
        std::cout << "不包含double值"<<std::endl;
    }
    
    //方法三,std::holds_alternative 检查当前存储的类型,该方法返回布尔型
    if(std::holds_alternative<double>(data))
    {
        cout<<"当前保存的是double"<<std::endl;
    }
    //方法四:std::visit 访问者模式
    std::visit([](const auto& value){
       std::cout<<"value = "<<value<<std::endl;     
},data)
}

常见的应用场景

思考:联合体(union)为什么不支持自动调用构造函数和析构函数?

假设有如下的联合体定义:

class MyClass
{
    private:
        int a;
        double b;
        std::string str;
        ...

}
union MyUnion {
    int x;
    std::string str;  // 包含非基本类型的 union
    Myclass myclass;  //自定义类型
};

由于联合体要按照数据成员的最大者申请空间,联合体的所有成员共享这片内存,现在上面的例子中有两个类,那么问题来了:

  1. 联合体应该按照哪个类的构造函数去构造?
  2. 如果每个这样的成员都构造一遍,会发生什么?

结果是:

  1. 编译器不知道构造联合体中那个成员
  2. 如果把类中所有非基本类型的成员都构造一遍,那么后构造的成员就会破坏之前构造的成员

所以我们一般不在联合体中使用非基本数据类型,如果一定要使用,就需要手动管理声明周期。

#include <string>

union MyUnion {
    int x;
    std::string str;
    
    // 默认构造函数
    MyUnion() : x(0) {} // 默认构造 int
    
    // 析构函数
    ~MyUnion() {} // 注意:这里不能自动析构 string,因为不知道当前是否存储的是 string
    
    // 禁用拷贝
    MyUnion(const MyUnion&) = delete;
    MyUnion& operator=(const MyUnion&) = delete;
};

int main() {
    MyUnion u;
    
    // 手动构造 string
    new (&u.str) std::string("Hello");
    
    // 使用 string
    std::cout << u.str << std::endl;
    
    // 手动析构 string
    u.str.~basic_string();
    
    // 现在可以使用 int
    u.x = 42;
}

这个问题在c++17引入std::variant特性引入后,就存在联合体存在的问题的了,c++在std::variant自动支持非基本数据类型了

思考:std::variant为什么是类型安全的?

编译时检查

在编译阶段会进行类型检查

std::variant<int, double> data;

data=1;  //正确
data = 3.14;  //正确
data = "hello";  //编译不通过

运行时检查

当通过一些方法从std::variant中获取值时,会抛出异常,而不会发生未定义的行为

std::variant<int, std::string> v = 42;

// 以下代码会抛出异常,而不是未定义行为
try {
    std::string s = std::get<std::string>(v);  // 抛出异常,因为当前存储的是int
} catch(const std::bad_variant_access&) {
    std::cout << "类型访问错误" << std::endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值