C++ 17 std::any
介绍
在 C++17 之前,如果我们想要在一个变量中存储任意类型的值,通常会使用 void*
指针或者通过类继承实现多态。但这些方法都有各自的缺点:void*
指针缺乏类型安全,而多态则需要预先定义继承关系。
为了解决这些问题,C++17 引入了 std::any
类型,它允许我们存储任意类型的单个值,而且是类型安全的。std::any
定义在 <any>
头文件中,属于标准库的一部分。
基本用法
创建和赋值
使用 std::any
非常简单,首先需要包含相应的头文件:
#include <any>
#include <iostream>
#include <string>
int main() {
// 创建一个空的 std::any
std::any a;
// 检查是否有值
std::cout << "a 是否有值? " << (a.has_value() ? "是" : "否") << std::endl;
// 赋值为整数
a = 42;
std::cout << "a 现在存储整数: " << std::any_cast<int>(a) << std::endl;
// 改变类型,赋值为字符串
a = std::string("Hello, std::any!");
std::cout << "a 现在存储字符串: " << std::any_cast<std::string>(a) << std::endl;
return 0;
}
输出:
a 是否有值? 否
a 现在存储整数: 42
a 现在存储字符串: Hello, std::any!
获取存储的值
要从 std::any
对象中获取存储的值,我们使用 std::any_cast
函数。如果尝试以错误的类型获取值,会抛出 std::bad_any_cast
异常:
#include <any>
#include <iostream>
#include <string>
int main() {
std::any a = 42;
try {
// 正确的类型转换
std::cout << std::any_cast<int>(a) << std::endl;
// 错误的类型转换,会抛出异常
std::cout << std::any_cast<double>(a) << std::endl;
}
catch (const std::bad_any_cast& e) {
std::cout << "类型转换失败: " << e.what() << std::endl;
}
return 0;
}
输出:
42
类型转换失败: bad any_cast
检查存储的类型
std::any
提供了 type()
方法,返回一个 std::type_info
对象,可以用来检查当前存储的值的类型:
#include <any>
#include <iostream>
#include <string>
#include <typeinfo>
int main() {
std::any a = 42;
std::cout << "a 存储的类型是: " << a.type().name() << std::endl;
a = std::string("Hello");
std::cout << "a 存储的类型是: " << a.type().name() << std::endl;
return 0;
}
type().name()
返回的字符串与编译器相关,可能不是很易读。例如,在 GCC 上,可能会返回像 i
(表示 int)或 NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
(表示 std::string)这样的输出。
进阶用法
存储自定义类型
std::any
可以存储任何可复制构造的类型,包括自定义类型:
#include <any>
#include <iostream>
#include <string>
class Person {
public:
Person(std::string name, int age) : name_(name), age_(age) {}
void print() const {
std::cout << "姓名: " << name_ << ", 年龄: " << age_ << std::endl;
}
private:
std::string name_;
int age_;
};
int main() {
std::any a = Person("张三", 30);
try {
std::any_cast<Person>(a).print();
// 也可以获取引用,避免复制
const Person& person = std::any_cast<const Person&>(a);
person.print();
}
catch (const std::bad_any_cast& e) {
std::cout << "类型转换失败: " << e.what() << std::endl;
}
return 0;
}
输出:
姓名: 张三, 年龄: 30
姓名: 张三, 年龄: 30
在容器中使用 std::any
std::any
可以与标准容器一起使用,创建可以存储不同类型元素的异构容器:
#include <any>
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::any> values;
values.push_back(42);
values.push_back(3.14);
values.push_back(std::string("Hello"));
values.push_back(true);
for (const auto& value : values) {
if (value.type() == typeid(int)) {
std::cout << "整数: " << std::any_cast<int>(value) << std::endl;
}
else if (value.type() == typeid(double)) {
std::cout << "浮点数: " << std::any_cast<double>(value) << std::endl;
}
else if (value.type() == typeid(std::string)) {
std::cout << "字符串: " << std::any_cast<std::string>(value) << std::endl;
}
else if (value.type() == typeid(bool)) {
std::cout << "布尔值: " << (std::any_cast<bool>(value) ? "true" : "false") << std::endl;
}
}
return 0;
}
输出:
整数: 42
浮点数: 3.14
字符串: Hello
布尔值: true
重置 std::any
可以使用 reset()
方法清除 std::any
对象中存储的值:
#include <any>
#include <iostream>
int main() {
std::any a = 42;
std::cout << "a 是否有值? " << (a.has_value() ? "是" : "否") << std::endl;
a.reset();
std::cout << "调用 reset() 后,a 是否有值? " << (a.has_value() ? "是" : "否") << std::endl;
return 0;
}
输出:
a 是否有值? 是
调用 reset() 后,a 是否有值? 否
实际应用场景
场景 1:插件系统
在插件系统中,std::any
可以用于传递不同类型的数据:
#include <any>
#include <iostream>
#include <string>
#include <unordered_map>
class PluginManager {
public:
void setParameter(const std::string& name, std::any value) {
parameters_[name] = std::move(value);
}
template<typename T>
T getParameter(const std::string& name) {
auto it = parameters_.find(name);
if (it != parameters_.end()) {
try {
return std::any_cast<T>(it->second);
}
catch (const std::bad_any_cast&) {
throw std::runtime_error("参数类型不匹配");
}
}
throw std::runtime_error("参数不存在");
}
private:
std::unordered_map<std::string, std::any> parameters_;
};
int main() {
PluginManager pm;
pm.setParameter("timeout", 30);
pm.setParameter("server", std::string("localhost"));
pm.setParameter("debug", true);
try {
int timeout = pm.getParameter<int>("timeout");
std::string server = pm.getParameter<std::string>("server");
bool debug = pm.getParameter<bool>("debug");
std::cout << "超时设置: " << timeout << " 秒\n";
std::cout << "服务器地址: " << server << "\n";
std::cout << "调试模式: " << (debug ? "开启" : "关闭") << std::endl;
}
catch (const std::exception& e) {
std::cout << "错误: " << e.what() << std::endl;
}
return 0;
}
输出:
超时设置: 30 秒
服务器地址: localhost
调试模式: 开启
场景 2:消息队列或事件系统
std::any
可以用于实现消息队列或事件系统,其中消息或事件可以携带不同类型的数据:
#include <any>
#include <iostream>
#include <queue>
#include <string>
struct Event {
std::string type;
std::any data;
};
class EventSystem {
public:
void pushEvent(const std::string& type, const std::any& data) {
events_.push(Event{type, data});
}
bool pollEvent(Event& event) {
if (events_.empty()) {
return false;
}
event = events_.front();
events_.pop();
return true;
}
private:
std::queue<Event> events_;
};
int main() {
EventSystem es;
// 发布事件
es.pushEvent("click", std::pair<int, int>(100, 200)); // 鼠标点击坐标
es.pushEvent("key_press", 'A'); // 按键事件
es.pushEvent("window_resize", std::pair<int, int>(800, 600)); // 窗口大小改变
// 处理事件
Event event;
while (es.pollEvent(event)) {
std::cout << "事件类型: " << event.type << std::endl;
if (event.type == "click") {
auto pos = std::any_cast<std::pair<int, int>>(event.data);
std::cout << "鼠标点击坐标: (" << pos.first << ", " << pos.second << ")" << std::endl;
}
else if (event.type == "key_press") {
char key = std::any_cast<char>(event.data);
std::cout << "按键: " << key << std::endl;
}
else if (event.type == "window_resize") {
auto size = std::any_cast<std::pair<int, int>>(event.data);
std::cout << "新窗口尺寸: " << size.first << "x" << size.second << std::endl;
}
std::cout << std::endl;
}
return 0;
}
输出:
事件类型: click
鼠标点击坐标: (100, 200)
事件类型: key_press
按键: A
事件类型: window_resize
新窗口尺寸: 800x600
std::any vs 其他类型擦除方案
与 void* 比较
std::any
提供类型安全,而void*
不提供std::any
管理对象的生命周期,而使用void*
需要手动管理内存std::any
支持任何可复制构造的类型,包括值类型和引用类型
与 std::variant 比较
std::any
可以存储任意类型,而std::variant
只能存储预定义的类型std::variant
通常更高效,因为它不使用动态内存分配- 当你知道所有可能的类型时,应该使用
std::variant
;当类型不确定时,使用std::any
与继承和多态比较
std::any
不需要预先定义类层次结构- 继承和多态通常更适合面向对象的设计,而
std::any
更适合通用容器和接口
性能考虑
使用 std::any
会带来一些性能开销:
- 动态内存分配:
std::any
通常会在堆上分配内存 - 类型擦除和恢复:需要额外的运行时检查
- 移动/复制开销:存储和获取值时会涉及复制操作
当性能是关键因素时,应该避免在热路径(频繁执行的代码路径)上过度使用 std::any
。
总结
std::any
是 C++17 引入的一个强大功能,它提供了一种类型安全的方式来存储任意类型的值。它主要具有以下特点:
- 可以存储任何可复制构造的类型
- 提供类型安全的值访问方式(通过
std::any_cast
) - 自动管理所存储对象的生命周期
- 适用于需要存储未知类型或多种不同类型的场景
虽然 std::any
很强大,但它并不适合所有场景。当你知道可能的类型集合时,std::variant
可能是更好的选择;当性能至关重要时,可能需要使用更轻量级的解决方案。
练习
- 编写一个简单的命令行计算器,使用
std::any
存储计算结果,支持整数和浮点数计算。 - 创建一个简单的配置系统,使用
std::unordered_map<std::string, std::any>
来存储不同类型的配置值。 - 实现一个简单的 JSON 解析器,使用
std::any
表示 JSON 值(可以是数字、字符串、数组、对象等)。
额外资源
- cppreference - std::any
- C++17 标准文档 - any
- CppCon 2016: Chandler Carruth "High Performance Code 201: Hybrid Data Structures" - 讨论了类型擦除和
std::any
的实现
通过学习 std::any
,你已经掌握了 C++17 中一个非常有用的特性,它能够帮助你编写更灵活、更通用的代码,同时保持类型安全。