类型安全的瑞士军刀——std::variant

2024-07-18 13:22:07 浏览数 (3)

前言

当需要在同一块内存区域中存储不同类型的值且在任何时刻只会存储其中的一种类型时,联合体(union)总是作为首要选择,但是联合体存在如类型安全差、不支持构造函数和析构函数等缺点。为避免union存在的问题,C 17引入一个非常实用且强大的新特性——std::variant。

std::variant作为一个多形态的容器,可以容纳一组预定义类型的其中之一,任何时候它都只存储其中一个类型的有效值,提供了严格的类型安全保证。

联合体通过.指定变量名进行变量存取,如下示例代码1。而std::variant型变量可以使用std::get<T>()和std::visit函数读取变量值

代码语言:javascript复制
//示例代码1

union MyUnion

{

    int i;

    float f;

    double d;

};



MyUnion u;
u.d =1.0;

写 std::variant变量

1. std::variant在未初始化时,默认调用第一个类型变量的默认构造函数,并将该值作为std::variant型变量的初值。如下示例验证

代码语言:javascript复制
struct  Point

{

    float x;

    float y;

    float z;

    friend std::ostream& operator<<(std::ostream& os,const Point& p)

    {

           return os<<p.x<<" "<<p.y<<" "<<p.z;

    }

};



int using_variant() {

    std::variant<Point,int, std::string, double> multiTypeVar;

    //函数对象

    std::visit([](auto val) {

        std::cout << "Value of type " << typeid(val).name() << ": " << val << std::endl;

        } , multiTypeVar);

    return 0;

}

//输出

//Value of type struct Point: 0 0 0

2. std::variant型变量赋值

可以使用emplace函数或=对std::variant型变量赋值

代码语言:javascript复制
int using_variant() {
    std::variant<int, std::string, double> multiTypeVar;
    multiTypeVar.emplace<double>(0.8);
    std::visit([](auto val) {
        std::cout << "Value of type " << typeid(val).name() << ": " << val << std::endl;
        }, multiTypeVar);


    multiTypeVar=10;
    std::visit([](auto val) {
        std::cout << "Value of type " << typeid(val).name() << ": " << val << std::endl;
        } , multiTypeVar);
    return 0;
}

读——使用std::get<T>

代码示例如下

代码语言:javascript复制
#include <variant>
#include <iostream>


int main() {
    std::variant<int, std::string> myVariant;

    myVariant = 42;
    std::cout << "初始值:" << std::get<int>(myVariant) << std::endl; // 输出"初始值:42"

    myVariant = "Hello, World!";
    std::cout << "字符串值:" << std::get<std::string>(myVariant) << std::endl; // 输出"字符串值:Hello, World!"


    // 通过index()函数获取当前存储值的类型索引
    if (myVariant.index() == 0) {
        std::cout << "当前存储的是int类型" << std::endl;
    } else if (myVariant.index() == 1) {
        std::cout << "当前存储的是std::string类型" << std::endl;
    }
   
    return 0;
}

上述代码展示了如何创建一个能存储int和std::string类型的std::variant,并根据需要在两者之间切换。值得注意的是,直接通过std::get<T>(myVariant)访问值时,必须确保当前存储的类型与T一致,否则会抛出std::bad_variant_access异常。

读——使用std::visit函数

std::visit函数为了更加安全地处理std::variant中的值,它接受一个可调用体(callable,函数对象/lambda表达式/std::function)和一个std::variant实例,根据variant中实际存储的类型调用访问者的相应重载方法。下面是一个使用std::visit的例子

代码语言:javascript复制
#include<variant>
#include<string>


struct VariantPrinter {
    template <typename T>
    void operator()(T&& value) const {
        std::cout << "Value of type " << typeid(T).name() << ": " << value << std::endl;
    }
};


int using_variant() {
    std::variant<int, std::string, double> multiTypeVar = 3.14;
    //函数对象
    std::visit(VariantPrinter{}, multiTypeVar); // 根据实际类型输出


    multiTypeVar = 10;
    //lambda表达式
    std::visit([](auto val){
        std::cout << "Value of type " << typeid(val).name() << ": " << val << std::endl;
        },
        multiTypeVar);


    return 0;
}

总结

std::variant以其类型安全性、内存高效性以及强大的多态处理能力,极大地丰富了C 程序设计的手段。熟练掌握这一特性,将有助于我们编写更为健壮、高效的代码。

0 人点赞