C++ std::variant 总结

server/2024/10/21 6:24:27/
C++ std::variant 总结

文章目录

        • 一、std::variant 的由来
        • 二、std::variant 用法简介
          • 2.1、类型安全保证
          • 2.2、访问 std::variant
        • 三、std::variant vs OO

 本文来记录一下对C++标准库的 std::variant (带标签的联合体)用法的总结。参考文章: hhttps://boolan.com/

一、std::variant 的由来

  根据 cppreference 官网的概念, std::variant 是类型安全的共用体。 C语言里面共用体是把不同类型的数据成员存储在同一块内存区域上, 它与结构体不一样, 结构体的每个成员都存储在独立的地址空间内, 所以对任何成员的写操作都不改变其他成员的值, 访问的时候也是基于基址偏移的方式进行读操作。
  共同体 union 改变任意一个成员的二进制值, 其余成员的值都会发生变化。考虑如下代码:


union TestUnion {int     m_int_value;float   m_float_value;
};int main() {TestUnion test_un;test_un.m_int_value = 666;std::cout << "Integer Value: " << test_un.m_int_value << "\n";test_un.m_float_value = 3.14f;std::cout << "Float Value: " << test_un.m_float_value << "\n";std::cout << "Integer Value: " << test_un.m_int_value << "\n";return 0;
}   

 此时输出如下所示, 当对以上代码中的浮点数成员进行修改后, 对应整数成员的二进制发生了变化, 此时访问整数类型成员是无效的, 但是访问整数型成员的没有任何警告或者异常。

Integer Value: 666
Float Value: 3.14
Integer Value: 1078523331

  除此之外还有一个问题, 那就是 C++ 里面有很多复杂类型, 当我们向共用体里新增复杂类型后, 维护起来就比较困难了。考虑如下代码:

union TestUnion {int     m_int_value;float   m_float_value;std::string m_string_value;
};

当向共用体里面添加一个 std::string 类型时, 直接编译不通过. 编译器无法判断该如何构造 std::string, 析构的时候该不该析构 std::string 成员, 所以它就方了. 原先存的都是POD类型,它们是没有构造和析构调用的, 如果想要编译通过就得手动维护共用体的构造和析构函数, 这就复杂了。

综上所述, 需要引入 std::variant.

二、std::variant 用法简介
2.1、类型安全保证

 先来看看它的类型安全保证, 代码如下:

int main() {std::variant<int, float> test_variant;test_variant = 10;std::cout << std::get<int>(test_variant) << std::endl;test_variant = 10.3f;std::cout << std::get<float>(test_variant) << std::endl;std::cout << std::get<int>(test_variant) << std::endl;return 0;
}

输出如下:

10
10.3
terminate called after throwing an instance of ‘std::bad_variant_access’
what(): std::get: wrong index for variant
Aborted

写入浮点数后再访问整数类型成员, 直接抛出了异常, 这提供了一个类型安全的保证。除此之外, 它能自动维护C++ 当中的复杂类型,也就是构造和析构函数的调用。

int main() {std::variant<int, float, std::string> test_variant;test_variant = 10;std::cout << std::get<int>(test_variant) << std::endl;test_variant = 10.3f;std::cout << std::get<float>(test_variant) << std::endl;test_variant = "test_variant";std::cout << std::get<std::string>(test_variant) << std::endl;return 0;
}

10
10.3
test_variant

2.2、访问 std::variant

 这里提供两种访问方式, 一种是预先的类型检测, 一种是采用指针的方式.

int main() {std::variant<int, float, std::string> test_variant;test_variant = "test_variant";if (std::holds_alternative<int>(test_variant) ){std::cout<<"hold int:" << std::get<int>(test_variant) <<std::endl;}else if(std::holds_alternative<float>(test_variant) ){std::cout<<"hold float:" << std::get<float>(test_variant) <<std::endl;}else if(std::holds_alternative<std::string>(test_variant) ){std::cout<<"hold std:;stirng:" << std::get<std::string>(test_variant) <<std::endl;}
}

hold std:;stirng:test_variant

基于指针的方式,代码如下:

    std::variant<int, float, std::string> test_variant;test_variant = 100.f;if (auto ptr = std::get_if<int>(&test_variant) ){std::cout<<"hold int:" << *ptr <<std::endl;}else if(auto ptr = std::get_if<float>(&test_variant) ){std::cout<<"hold float:" << *ptr <<std::endl;}else if(auto ptr = std::get_if<std::string>(&test_variant) ){std::cout<<"hold std:;stirng:" << *ptr <<std::endl;}

hold float:100

cppreference 里面还提供了一种更牛逼的遍历方式, 代码如下:


using var_t = std::variant<int, long, double, std::string>;/*
这里首先是声明一个可变模板参数的函数模板 overloaded, 再显示的推导为 overloaded 结构体
overloaded 也是一个可变模板参数的模板结构体, 通过继承所有模板参数, 再结合 using Ts::operator()...
实现对基类, 也就是模板参数中的 operator() 运算符的声明。 此时就可以根据 vec 列表的成员类型调用合适的 lambda 表达式了
*/template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;int main()
{std::vector<var_t> vec = {10, 15l, 1.5, "hello"};for (auto& v: vec) {std::visit(overloaded{[](auto arg) { std::cout << arg << ' '; },[](double arg) { std::cout << std::fixed << arg << ' '; },[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }}, v);}
}
三、std::variant vs OO

  std::variant 在某种情况下可以作为多态的一种替换选择。


namespace{int sum_count = 0;  const int kShapeCount {50000};
};class Shape
{
public:virtual ~Shape() =default;virtual void DoSomething() = 0;
};class Cricle:public Shape
{
public:void DoSomething() override;
};class Rectange:public Shape
{
public:void DoSomething()override;
};class CricleEX
{
public:void DoSomething(){};
};class RectangeEX
{
public:void DoSomething(){};
};int main() {std::vector<std::variant<CricleEX, RectangeEX> > ex_shape_vecs;for(int i{0}; i<kShapeCount; i++){ex_shape_vecs.emplace_back(CricleEX() );ex_shape_vecs.emplace_back(RectangeEX() );}auto t1 = std::chrono::steady_clock::now();for(auto& itor: ex_shape_vecs){std::visit([](auto& obj){obj.DoSomething();}, itor);}auto t2 = std::chrono::steady_clock::now();std::cout << "std::varint cost:" <<std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t2-t1).count() <<"ms" <<std::endl;std::vector<std::unique_ptr<Shape> > shape_vecs;for(int i{0}; i<kShapeCount; i++){shape_vecs.emplace_back(std::make_unique<Cricle>() );shape_vecs.emplace_back(std::make_unique<Rectange>() );}auto t3 = std::chrono::steady_clock::now();for(const auto& itor: shape_vecs){itor->DoSomething();}auto t4 = std::chrono::steady_clock::now();std::cout << "OO cost:" <<std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t4-t3).count() <<"ms" <<std::endl;std::cout<<"sumcount:"<<sum_count<<std::endl;return 0;
}

std::varint cost:7.94071ms
OO cost:4.19324ms
sumcount:500000

很奇怪,这里我得到的结论是面向对象的方式要比 std::variat 效率高, 这与吴咏伟老师在课堂上的当时出现了相反的结论, 这个难道是编译优化选项有啥不同哇, 大家觉得呢?


http://www.ppmy.cn/server/19928.html

相关文章

AcWing 801. 二进制中1的个数——算法基础课题解

AcWing 801. 二进制中 1 的个数 题目描述 给定一个长度为 n 的数列&#xff0c;请你求出数列中每个数的二进制表示中 1 的个数。 输入格式 第一行包含整数 n。 第二行包含 n 个整数&#xff0c;表示整个数列。 输出格式 共一行&#xff0c;包含 n 个整数&#xff0c;其中…

玩转B2B2C,智能名片小程序带你开启“撩”客新纪元!

在这个数字化浪潮汹涌的时代&#xff0c;你还在为如何快速有效地抓住客户而焦头烂额吗&#xff1f;嘿嘿&#xff0c;别担心&#xff0c;现在有了我们的B2B2C AI智能名片小程序&#xff0c;一切都变得轻而易举&#xff01;这款小程序&#xff0c;就像是一个精通社交的“撩客达人…

第29篇 分布式网站

大型分布式网站架构是指将一个网站系统分解为多个独立的组件或服务&#xff0c;这些组件或服务部署在不同的物理或虚拟机器上&#xff0c;协同工作以提供高效、可靠且可扩展的网站功能。这种架构设计旨在应对高并发访问、处理海量数据、保证服务高可用性、快速响应业务变化及增…

网络安全实训Day15

写在前面 电子垃圾&#xff0c;堂堂恢复连载。本来不想分天数梳理了&#xff0c;但是最后要写实训报告&#xff0c;报告里还要有实训日记记录每日学的东西&#xff0c;干脆发这里留个档&#xff0c;到时候写报告提供一个思路。 网络空间安全实训-渗透测试 渗透测试概述 定义 一…

Hive——DML(Data Manipulation Language)数据操作语句用法详解

DML 1.Load Load语句可将文件导入到Hive表中。 hive> LOAD DATA [LOCAL] INPATH filepath [OVERWRITE] INTO TABLE tablename [PARTITION (partcol1val1, partcol2val2 ...)];关键字说明&#xff1a; local&#xff1a;表示从本地加载数据到Hive表&#xff1b;否则从HD…

MIT6.824|课程前置知识

为了让技术栈更宽一点&#xff0c;想学习一下分布式系统&#xff0c;顺便下定决心学习一下go语言吧&#xff01; 由于还没有开始学&#xff0c;这里只总结相关的资料&#xff0c;后续会慢慢进化成MIT6.824课程导学。 Go语言 MIT6.824课程是用go语言来完成的&#xff0c;所以了…

机器人系统能用MQTT5.0代替ROS2吗?

前言 ROS2是目前最主流的机器人系统&#xff0c;但由于ROS2的学习曲线比较徒陗&#xff0c;而且对于资源受限的系统并不友好&#xff1b;而MQTT5.0是最新的MQTT消息传输协议&#xff0c;为现代IoT提供了更友好的支持&#xff0c;下面讨论MQTT5.0和ROS2结合使用&#xff0c;或机…

前端提高篇(二十四)JS进阶18对象属性的高级用法

x:1, y:2, } Object.defineProperty(obj1, ‘z’,{ value:3, writable:true, enumerable:true, configurable:true, }) for (var i in obj1){ console.log(i ’ : ’ obj1[i]); } 运行效果&#xff1a; 不可枚举时&#xff1a; var obj1 { x:1, y:2, } Obj…