深入探索C++17的std::any:类型擦除与泛型编程的利器

生成特定比例的图片 (2).png

文章目录

    • 基本概念
    • 构建方式
      • 构造函数直接赋值
      • std::make_any
      • std::in_place_type
    • 访问值
      • 值转换
      • 引用转换
      • 指针转换
    • 修改器
      • emplace
      • reset
      • swap
    • 观察器
      • has_value
      • type
    • 使用场景
      • 动态类型的API设计
      • 类型安全的容器
      • 简化类型擦除实现
    • 性能考虑
      • 动态内存分配
      • 类型转换和异常处理
    • 总结

在C++17的标准库中, std::any作为一个全新的特性,为开发者们带来了前所未有的灵活性。它是一种通用类型包装器,能够在运行时存储任意类型的值,为C++的类型系统和容器库增添了强大的功能。这篇文章将深入探讨 std::any的各个方面,包括基本概念、构建方式、访问值的方法、修改器和观察器的使用、实际应用场景以及性能考虑。

基本概念

std::any是一个非模板类,它允许在运行时存储任意类型的单个值,前提是该类型满足可复制构造和可移动构造的要求。与传统的void*指针不同,std::any提供了类型安全的存储和访问机制。它通过类型擦除的方式,隐藏了存储对象的具体类型信息,使得我们可以在不关心具体类型的情况下进行数据的存储和传递。

构建方式

构造函数直接赋值

最直观的初始化方式就是通过构造函数直接赋值。例如:

std::any a = 10; // 存储一个int类型的值
std::any b = 3.14; // 存储一个double类型的值

这样的方式简单易懂,适用于简单类型的初始化。

std::make_any

std::make_any是一个函数模板,它以更显式的方式指定初始化的类型,并通过完美转发来构造对象。这不仅提高了代码的可读性,还在某些情况下具有更好的性能。例如:

auto a0 = std::make_any<std::string>("Hello, std::any!");
auto a1 = std::make_any<std::vector<int>>({1, 2, 3});

std::make_any通常会利用对象的就地构造特性,避免不必要的临时对象创建,从而提高效率。

std::in_place_type

std::in_place_type用于在构造std::any对象时指明类型,并允许使用多个参数初始化对象。这对于需要调用带参数构造函数的类型非常有用。例如:

class Complex {
public:double real, imag;Complex(double r, double i) : real(r), imag(i) {}
};
std::any m_any_complex{std::in_place_type<Complex>, 1.0, 2.0};

访问值

值转换

std::any_cast以值的方式返回存储的值时,会创建一个临时对象。例如:

std::any a = 42;
try {int value = std::any_cast<int>(a);std::cout << "The value of a is " << value << std::endl;
} catch (const std::bad_any_cast& e) {std::cout << "Attempted to cast to incorrect type" << std::endl;
}

这种方式适用于不需要修改原始值,并且对性能要求不是特别高的场景。

引用转换

通过引用转换可以避免创建临时对象,并且可以直接修改存储的值。例如:

std::any b = std::string("Hello");
try {std::string& ref = std::any_cast<std::string&>(b);ref.append(" World!");std::cout << "The modified string is " << ref << std::endl;
} catch (const std::bad_any_cast& e) {std::cout << "Attempted to cast to incorrect type" << std::endl;
}

使用引用转换时,必须确保std::any对象确实存储了目标类型的值,否则会抛出std::bad_any_cast异常。

指针转换

指针转换方式在类型不匹配时会返回nullptr,而不是抛出异常。例如:

std::any c = 100;
int* ptr = std::any_cast<int>(&c);
if (ptr) {std::cout << "The value pointed by ptr is " << *ptr << std::endl;
} else {std::cout << "Type mismatch" << std::endl;
}

这种方式在需要更稳健地处理类型不匹配情况时非常有用。

修改器

emplace

emplace用于在std::any内部直接构造新对象,而无需先销毁旧对象再创建新对象。这在需要频繁修改存储值的场景中可以提高性能。例如:

std::any celestial;
celestial.emplace<Star>("Procyon", 2943);

这里假设Star是一个自定义类,具有带参数的构造函数。

reset

reset方法用于销毁std::any中存储的对象,并将其状态设置为空。这可以释放对象占用的资源。例如:

std::any data = std::string("Some data");
data.reset();
if (!data.has_value()) {std::cout << "std::any is now empty" << std::endl;
}

swap

swap方法用于交换两个std::any对象的值。这在需要交换不同类型数据的场景中非常方便。例如:

std::any a = 10;
std::any b = "Hello";
a.swap(b);
try {std::cout << "a now holds: " << std::any_cast<const char*>(a) << std::endl;std::cout << "b now holds: " << std::any_cast<int>(b) << std::endl;
} catch (const std::bad_any_cast& e) {std::cout << "Cast error: " << e.what() << std::endl;
}

观察器

has_value

has_value方法用于检查std::any是否存储了值。这在进行类型转换之前非常有用,可以避免不必要的异常抛出。例如:

std::any maybeValue;
if (maybeValue.has_value()) {try {int value = std::any_cast<int>(maybeValue);std::cout << "Value is: " << value << std::endl;} catch (const std::bad_any_cast& e) {std::cout << "Cast error: " << e.what() << std::endl;}
} else {std::cout << "std::any is empty" << std::endl;
}

type

type方法返回存储值的类型信息,如果std::any为空,则返回typeid(void)。这可以用于在运行时进行类型检查。例如:

std::any a = 42;
if (a.type() == typeid(int)) {std::cout << "The stored type is int" << std::endl;
}

使用场景

动态类型的API设计

在事件处理系统中,不同类型的事件可能携带不同类型的数据。使用std::any可以设计一个通用的事件处理函数,能够处理各种类型的事件数据。

class Event {
public:std::string name;std::any data;
};void handleEvent(const Event& event) {if (event.name == "MouseClick") {try {std::pair<int, int> coords = std::any_cast<std::pair<int, int>>(event.data);std::cout << "Mouse clicked at (" << coords.first << ", " << coords.second << ")" << std::endl;} catch (const std::bad_any_cast& e) {std::cout << "Invalid data type for MouseClick event" << std::endl;}} else if (event.name == "FileLoad") {try {std::string filename = std::any_cast<std::string>(event.data);std::cout << "Loading file: " << filename << std::endl;} catch (const std::bad_any_cast& e) {std::cout << "Invalid data type for FileLoad event" << std::endl;}}
}

类型安全的容器

std::any可以用于创建能够存储不同类型数据的容器,同时保持类型安全。例如,一个配置文件解析器可能需要存储不同类型的配置项。

std::vector<std::any> config;
config.push_back(10); // 存储一个整数配置项
config.push_back("default_path"); // 存储一个字符串配置项
config.push_back(true); // 存储一个布尔配置项for (const auto& item : config) {if (item.type() == typeid(int)) {int value = std::any_cast<int>(item);std::cout << "Integer config: " << value << std::endl;} else if (item.type() == typeid(std::string)) {std::string value = std::any_cast<std::string>(item);std::cout << "String config: " << value << std::endl;} else if (item.type() == typeid(bool)) {bool value = std::any_cast<bool>(item);std::cout << "Boolean config: " << (value? "true" : "false") << std::endl;}
}

简化类型擦除实现

在模块化编程中,不同模块之间可能需要传递数据,但某些模块可能不关心数据的具体类型。使用std::any可以隐藏数据的具体类型信息,实现类型擦除。例如,一个日志模块可能只需要记录数据,而不需要知道数据的具体类型。

class Logger {
public:void log(const std::any& data) {if (data.type() == typeid(int)) {int value = std::any_cast<int>(data);std::cout << "Logged integer: " << value << std::endl;} else if (data.type() == typeid(std::string)) {std::string value = std::any_cast<std::string>(data);std::cout << "Logged string: " << value << std::endl;}}
};Logger logger;
logger.log(42);
logger.log("Hello, logging!");

性能考虑

动态内存分配

std::any的实现通常涉及动态内存分配,因为它需要存储不同类型的对象,而这些对象的大小在编译时是未知的。这意味着在频繁创建和销毁std::any对象的场景中,会产生显著的内存分配和释放开销。例如,在一个循环中大量创建std::any对象来存储临时数据,可能会导致性能下降。

类型转换和异常处理

频繁的类型转换操作,尤其是使用std::any_cast进行值转换时创建临时对象,会带来额外的性能开销。此外,异常处理机制也会增加代码的执行时间,特别是在转换失败频繁发生的情况下。因此,在性能敏感的代码中,应该尽量减少不必要的类型转换,并通过合理的类型检查来避免异常抛出。

总结

std::any为C++开发者提供了强大的类型擦除和泛型编程能力,使得在处理不同类型数据时更加灵活和安全。通过深入理解其构建方式、访问值的方法、修改器和观察器的功能,以及在各种实际场景中的应用,开发者可以更好地利用std::any来优化代码结构。同时,要充分认识到其性能特点,在性能敏感的场景中谨慎使用,以确保程序的高效运行。

希望这篇文章能够帮助你全面深入地理解std::any在C++17中的使用,为你的C++编程之旅增添一份助力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/8944.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DeepSeek-R1 蒸馏模型及如何用 Ollama 在本地运行DeepSeek-R1

在人工智能飞速发展的领域中&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的出现可谓是一项重大变革。在这些模型里&#xff0c;DeepSeek - R1 及其蒸馏模型备受瞩目&#xff0c;它们融合了独特的能力与高可用性。今天我们一起聊一下 DeepSeek - R1 蒸馏模型究竟是什么…

机器学习day3

自定义数据集使用框架的线性回归方法对其进行拟合 import matplotlib.pyplot as plt import torch import numpy as np # 1.散点输入 # 1、散点输入 # 定义输入数据 data [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1]…

java多线程学习笔记

文章目录 关键词1.什么是多线程以及使用场景?2.并发与并行3.多线程实现3.1继承 Thread 类实现3.2Runnable 接口方式实现3.3Callable接口/Future接口实现3.4三种方式总结 4.常见的成员方法&#xff08;重点记忆&#xff09;94.1setName/currentThread/sleep要点4.2线程的优先级…

无耳科技 Solon v3.0.7 发布(2025农历新年版)

Solon 框架&#xff01; Solon 框架由杭州无耳科技有限公司&#xff08;下属 Noear 团队&#xff09;开发并开源。是新一代&#xff0c;面向全场景的 Java 企业级应用开发框架。从零开始构建&#xff08;非 java-ee 架构&#xff09;&#xff0c;有灵活的接口规范与开放生态。…

Redis常用命令合集【一】

1.Redis常用命令 Redis是典型的key-value数据库&#xff0c;key一般是字符串&#xff0c;而value包含很多不同的数据类型&#xff1a; Redis为了方便我们学习&#xff0c;将操作不同数据类型的命令也做了分组&#xff0c;在官网&#xff08; https://redis.io/commands &#…

python学opencv|读取图像(四十八)使用cv2.bitwise_xor()函数实现图像按位异或运算

【0】基础定义 按位与运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;全1取1&#xff0c;其余取0。 按位或运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;有1取1&#xff0c;其余取0。 按位取反运算&#xff1a;一个二进制数&#xff0c;0变1,1变0。 按…

docker 学习笔记

一、docker容器快速上手以及简单操作 docker的image和container image镜像 docker image就是一个read.only文件&#xff0c;可以理解成一个模版&#xff0c;docker image具有分层的概念 可以自己制作&#xff0c;也可以从registry拉去 container容器 一个运行中的docker …

【PyTorch】5.张量索引操作

目录 1. 简单行、列索引 2. 列表索引 3. 范围索引 4. 布尔索引 5. 多维索引 个人主页&#xff1a;Icomi 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&#xff0c;为构建和训练神经网络提供了高效且灵活的平台。神经网络作为…

穿心莲内酯(andrographolide)生物合成CYP72-文献精读106

Two CYP72 enzymes function as Ent-labdane hydroxylases in the biosynthesis of andrographolide in Andrographis paniculata 两种CYP72酶在穿心莲&#xff08;Andrographis paniculata&#xff09;中作为Ent-labdane羟化酶&#xff0c;在穿心莲内酯&#xff08;andrograp…

关于圆周率的新认知 - 2

当未知长度的单位 1 和已完成长度的单位 1 之间的比例不是 1:1 而是其它的数值的时候&#xff0c;不难看出&#xff0c;这时候的圆周率就变成了“椭圆周率”。你可能要说&#xff0c;这不是椭圆积分吗&#xff1f;对了&#xff0c;这就是椭圆积分。但是我们不要考虑什么椭圆积分…

ARM64平台Flutter环境搭建

ARM64平台Flutter环境搭建 Flutter简介问题背景搭建步骤1. 安装ARM64 Android Studio2. 安装Oracle的JDK3. 安装 Dart和 Flutter 开发插件4. 安装 Android SDK5. 安装 Flutter SDK6. 同意 Android 条款7. 运行 Flutter 示例项目8. 修正 aapt2 报错9. 修正 CMake 报错10. 修正 N…

进程池的制作(linux进程间通信,匿名管道... ...)

目录 一、进程间通信的理解 1.为什么进程间要通信 2.如何进行通信 二、匿名管道 1.管道的理解 2.匿名管道的使用 3.管道的五种特性 4.管道的四种通信情况 5.管道缓冲区容量 三、进程池 1.进程池的理解 2.进程池的制作 四、源码 1.ProcessPool.hpp 2.Task.hpp 3…

新年祝词(原创)

新年将至&#xff0c;福进万户。 家家团圆&#xff0c;事事顺心。 喜迎财神&#xff0c;多寿添金。 瑞兽迎春&#xff0c;炮竹声起。 趋吉避凶&#xff0c;蛇年大吉。 中华崛起&#xff0c;人人自强。 天下大同&#xff0c;百姓富足。 有情有义&#xff0c;平易近人。 …

stack 和 queue容器的介绍和使用

1.stack的介绍 1.1stack容器的介绍 stack容器的基本特征和功能我们在数据结构篇就已经详细介绍了&#xff0c;还不了解的uu&#xff0c; 可以移步去看这篇博客哟&#xff1a; 数据结构-栈数据结构-队列 简单回顾一下&#xff0c;重要的概念其实就是后进先出&#xff0c;栈在…

python:洛伦兹变换

洛伦兹变换&#xff08;Lorentz transformations&#xff09;是相对论中的一个重要概念&#xff0c;特别是在讨论时空的变换时非常重要。在四维时空的背景下&#xff0c;洛伦兹变换描述了在不同惯性参考系之间如何变换时间和空间坐标。在狭义相对论中&#xff0c;洛伦兹变换通常…

DIY QMK量子键盘

最近放假了&#xff0c;趁这个空余在做一个分支项目&#xff0c;一款机械键盘&#xff0c;量子键盘取自固件名称QMK&#xff08;Quantum Mechanical Keyboard&#xff09;。 键盘作为计算机或其他电子设备的重要输入设备之一&#xff0c;通过将按键的物理动作转换为数字信号&am…

【Unity3D】aab包太大无法上传Google问题

目录 一、勾选Split Application Binary&#xff0c;Unity直接打aab包 勾选Split Application Binary选项的影响 不勾选Split Application Binary选项的影响 总结 2、导出Android工程打包aab 一、勾选Split Application Binary&#xff0c;Unity直接打aab包 超出150MB部分…

DeepSeek助力学术文献搜索!

搜集文献 宝子们如果是第一次发表学术论文&#xff0c;论文往往是会署名多个作者。在这种情况下&#xff0c;即便成功发表了论文&#xff0c;独立撰作或主导写作的挑战仍旧存在。那么&#xff0c;怎样才能独立地完成一篇属于自己的学术论文呢&#xff1f;对于初次尝试学术论文…

【时时三省】(C语言基础)文件的随机读写

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 fseek 根据文件指针的位置和偏移量来定位文件指针 示例&#xff1a; 这个输出的就是ade seek&#xff3f;cur的意思是从当前偏移量 2就是从a往后偏移两个就是d 偏移量 SEEK&#xff3f;CUR…

Python-基于PyQt5,json和playsound的通用闹钟

前言&#xff1a;刚刚结束2024年秋季学期的学习&#xff0c;接下来我们继续来学习PyQt5。由于之前我们已经学习了PyQt5以及PyUIC,Pyrcc和QtDesigner的安装&#xff0c;配置。所以接下来我们一起深入PyQt5&#xff0c;学习如何利用PyQt5进行实际开发-基于PyQt5&#xff0c;json和…