深入探索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.mzph.cn/bicheng/69448.shtml

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

相关文章

物管系统赋能智慧物业管理提升服务质量与工作效率的新风潮

内容概要 在当今的物业管理领域&#xff0c;物管系统的崛起为智慧物业管理带来了新的机遇和挑战。这些先进的系统能够有效整合各类信息&#xff0c;促进数字化管理&#xff0c;从而提升服务质量和工作效率。通过物管系统&#xff0c;物业管理者可以实时查看和分析各种数据&…

分组表格antd+ react +ts

import React from "react"; import { Table, Tag } from "antd"; import styles from "./index.less"; import GroupTag from "../Tag"; const GroupTable () > {const columns [{title: "姓名",dataIndex: "nam…

【JAVA实战】如何使用 Apache POI 在 Java 中写入 Excel 文件

大家好&#xff01;&#x1f31f; 在这篇文章中&#xff0c;我们将带你深入学习如何使用 Apache POI 在 Java 中编写 Excel 文件的技巧&#xff01;&#x1f4ca;&#x1f4da; 如果你是 Java 开发者&#xff0c;或者正在探索如何处理 Excel 文件的数据&#xff0c;那么这篇文章…

使用Avalonia UI实现DataGrid

1.Avalonia中的DataGrid的使用 DataGrid 是客户端 UI 中一个非常重要的控件。在 Avalonia 中&#xff0c;DataGrid 是一个独立的包 Avalonia.Controls.DataGrid&#xff0c;因此需要单独通过 NuGet 安装。接下来&#xff0c;将介绍如何安装和使用 DataGrid 控件。 2.安装 Dat…

C#分页思路:双列表数据组合返回设计思路

一、应用场景 需要分页查询&#xff08;并非全表查载入物理内存再筛选&#xff09;&#xff0c;返回列表1和列表2叠加的数据时 二、实现方式 列表1必查&#xff0c;列表2根据列表1的查询结果决定列表2的分页查询参数 三、示意图及其实现代码 1.示意图 黄色代表list1的数据&a…

【Linux】磁盘

没有被打开的文件 文件在磁盘中的存储 认识磁盘 磁盘的存储构成 磁盘的效率 与磁头运动频率有关。 磁盘的逻辑结构 把一面展开成线性。 通过扇区的下标编号可以推算出在磁盘的位置。 磁盘的寄存器 控制寄存器&#xff1a;负责告诉磁盘是读还是写。 数据寄存器&#xff1a;给…

第13章 深入volatile关键字(Java高并发编程详解:多线程与系统设计)

1.并发编程的三个重要特性 并发编程有三个至关重要的特性&#xff0c;分别是原子性、有序性和可见性 1.1 原子性 所谓原子性是指在一次的操作或者多次操作中&#xff0c;要么所有的操作全部都得到了执行并 且不会受到任何因素的干扰而中断&#xff0c;要么所有的操作都不执行…

记录 | Docker的windows版安装

目录 前言一、1.1 打开“启用或关闭Windows功能”1.2 安装“WSL”方式1&#xff1a;命令行下载方式2&#xff1a;离线包下载 二、Docker Desktop更新时间 前言 参考文章&#xff1a;Windows Subsystem for Linux——解决WSL更新速度慢的方案 参考视频&#xff1a;一个视频解决D…

stack 和 queue容器的介绍和使用

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

JUC--ConcurrentHashMap底层原理

ConcurrentHashMap底层原理 ConcurrentHashMapJDK1.7底层结构线程安全底层具体实现 JDK1.8底层结构线程安全底层具体实现 总结JDK 1.7 和 JDK 1.8实现有什么不同&#xff1f;ConcurrentHashMap 中的 CAS 应用 ConcurrentHashMap ConcurrentHashMap 是一种线程安全的高效Map集合…

C++17 std::variant 详解:概念、用法和实现细节

文章目录 简介基本概念定义和使用std::variant与传统联合体union的区别 多类型值存储示例初始化修改判断variant中对应类型是否有值获取std::variant中的值获取当前使用的type在variant声明中的索引 访问std::variant中的值使用std::get使用std::get_if 错误处理和访问未初始化…

NLP自然语言处理通识

目录 ELMO 一、ELMo的核心设计理念 1. 静态词向量的局限性 2. 动态上下文嵌入的核心思想 3. 层次化特征提取 1. 双向语言模型&#xff08;BiLM&#xff09; 2. 多层LSTM的层次化表示 三、ELMo的运行过程 1. 预训练阶段 2. 下游任务微调 四、ELMo的突破与局限性 1. 技术突破 2. …

在做题中学习(82):最小覆盖子串

解法&#xff1a;同向双指针——>滑动窗口 思路&#xff1a;题目要求找到s里包含t所有字符的最小子串&#xff0c;这就需要记录在s中每次查找并扩大范围时所包含进去的字符种类是否和t的相同&#xff0c;并且&#xff1a;题目提示t中会有重复字符&#xff0c;因此不能简单认…

【deepseek】deepseek-r1本地部署-第二步:huggingface.co替换为hf-mirror.com国内镜像

一、背景 由于国际镜像国内无法直接访问&#xff0c;会导致搜索模型时加载失败&#xff0c;如下&#xff1a; 因此需将国际地址替换为国内镜像地址。 二、操作 1、使用vscode打开下载路径 2、全局地址替换 关键字 huggingface.co 替换为 hf-mirror.com 注意&#xff1a;务…

DeepSeek:突破传统的AI算法与下载排行分析

DeepSeek的AI算法突破DeepSeek相较于OpenAI以及其它平台的性能对比DeepSeek的下载排行分析&#xff08;截止2025/1/28 AI人工智能相关DeepSeek甚至一度被推上了搜索&#xff09;未来发展趋势总结 在人工智能技术飞速发展的当下&#xff0c;搜索引擎市场也迎来了新的变革。DeepS…

java 判断Date是上午还是下午

我要用Java生成表格统计信息&#xff0c;如下图所示&#xff1a; 所以就诞生了本文的内容。 在 Java 里&#xff0c;判断 Date 对象代表的时间是上午还是下午有多种方式&#xff0c;下面为你详细介绍不同的实现方法。 方式一&#xff1a;使用 java.util.Calendar Calendar 类…

【Matlab高端绘图SCI绘图模板】第05期 绘制高阶折线图

1.折线图简介 折线图是一个由点和线组成的统计图表&#xff0c;常用来表示数值随连续时间间隔或有序类别的变化。在折线图中&#xff0c;x 轴通常用作连续时间间隔或有序类别&#xff08;比如阶段1&#xff0c;阶段2&#xff0c;阶段3&#xff09;。y 轴用于量化的数据&#x…

【Java数据结构】了解排序相关算法

基数排序 基数排序是桶排序的扩展&#xff0c;本质是将整数按位切割成不同的数字&#xff0c;然后按每个位数分别比较最后比一位较下来的顺序就是所有数的大小顺序。 先对数组中每个数的个位比大小排序然后按照队列先进先出的顺序分别拿出数据再将拿出的数据分别对十位百位千位…

Linux的常用指令的用法

目录 Linux下基本指令 whoami ls指令&#xff1a; 文件&#xff1a; touch clear pwd cd mkdir rmdir指令 && rm 指令 man指令 cp mv cat more less head tail 管道和重定向 1. 重定向&#xff08;Redirection&#xff09; 2. 管道&#xff08;Pipes&a…

Ubuntu20.04 磁盘空间扩展教程

Ubuntu20.04 磁盘空间扩展教程_ubuntu20 gpart扩容-CSDN博客文章浏览阅读2w次&#xff0c;点赞38次&#xff0c;收藏119次。执行命令查看系统容量相关的数据&#xff1a;df -h当前容量为20G&#xff0c;已用18G&#xff08;96%&#xff09;&#xff0c;可用844M&#xff0c;可用…