std::any
概念
值类型
一般来说,C++是一门类型绑定和类型安全的语言。
- 值对象:被声明为确定的类型,并且不能改变自身的类型。
- 类型:定义了所有可能的操作、也定义了对象的行为。
std::any是一种在保证类型安全的基础上还能改变自身类型的值类型 —— 同时包含了值和值的类型
- std::any可以持有任意类型的值
- std::any知道自己当前持有的值是什么类型
声明std::any类型的对象时无需指明所有可能的类型,因此内含的值可以有任意的大小
-
必须有可以在堆上分配内存的能力
-
尽管如此,还是会尽量避免为小类型的值在堆上分配内存(例如 int)
-
因此拷贝 std::any的开销一般都很大。更推荐以引用 或者 移动语义 传递对象 —— std::any支持部分 move语义。
-
-
可以使用运行时检查来判断当前的值的类型。
目的
std::any的设计目标:
- 提供一种类型安全且易于使用的方式来在运行时处理各种类型的数据
- 任何错误的类型转换都会在运行时抛出异常。
std::any不是模版类,是用于任何 可拷贝构造类型的单个值
的类型安全容器。
-
只能容纳一个元素
-
一种动态类型变量,可以存储任何类型的值
-
基本数据类型(int,double,char,float…)
-
复合数据类型(类、结构体)。
-
-
值类型
- 可查询类型:当前保存的是哪种类型的值
- 可更改类型:更改后仍具有类型安全性。
缺点
-
性能不如其他类型,运行时需要进行类型检查和类型转换。
-
没有定义比较运算符(不能比较或者排序对象),哈希函数,value()成员函数。
-
编译期不知道存储的类型 —— 不能使用泛型 lambda来独立于类型处理当前的值,处理新类型需要不断的人工加入。
-
运行时也不知道存储的具体类型(只能依赖 RTTI进行比较),
any_cast
使用时需要显式地进行类型转换。
构建方式
构造函数
std::any的构造函数使用了 std::decay_t
会导致,诸如:
- 数组
T[N]
退化成T*
- 函数 退化成 函数指针
所以实际使用中,建议跳过这一节,使用显式指定类型的方法(见下一章节的std::in_place_type
,std::make_any
)。
在声明此类型的对象时,不需要指定可能的类型。
std::any m_any_a; // m_any_a为空
当然,也可以赋初值
// 隐式推导
std::any m_any_a;
m_any_a= 42; // a有类型int, 值42std::any m_any_b = 4.3; // 有类型double, 值4.3
m_any_b = std::string{"hi"}; // 有类型std::string, 值"hi"std::any m_any_str = "wegrthweg"; //type : const char*// 手动创建对象
std::any a1{MyClass{6.0, 2.0}};
运行时可以配合 RTTI (Run-Time Type Information) 使用:
if (m_any_a.type() == typeid(std::string)) {std::string s = std::any_cast<std::string>(m_any_a);useString(s);
}
else if (m_any_a.type() == typeid(int)) {useInt(std::any_cast<int>(m_any_a));
}
operator=
//[1]
any& operator=( const any& rhs );
//[2]
any& operator=( any&& rhs ) noexcept;
//[3]
template<typename ValueType>
any& operator=( ValueType&& rhs );
指明类型
std::in_place_type
将std::in_place_type<T>
作为第一个参数
- 用于指明类型(可以覆盖原类型)
- 允许使用多个参数初始化对象
可以使用 in_place_type标记,以使内部值的类型和初始值的类型不同。
std::any m_any_int{std::in_place_type<int>, 420};
// std::in_place_type作为第一个参数
// - std::in_place_type + 可变参数
std::any a2{std::in_place_type<MyClass>, 6.0, 2.0};
// - std::in_place_type + 初始化列表
std::any a2{std::in_place_type<set<int>>, {3, 7, -1, -16, 1, 100}};auto func = [] (int x, int y) { return std::abs(x) < std::abs(y);};
std::any m_any_set{std::in_place_type<std::set<int,decltype(func)>>, {3, 7, -1, -16, 1, 100}, func
};
多参数初始化对象
auto sc = [] (int x, int y) {return std::abs(x) < std::abs(y);
};
std::any m_any_set{std::in_place_type <std::set<int, decltype(sc)>>, {4, 8, ‐7, ‐2, 0, 5}, sc};
指明类型后,甚至可以是lambda
std::any any_lambda {std::in_place_type<std::function<void(void)>>,[] { std::cout << "Lambda #1.\n"; }
};
std::make_any
显式指定初始化的类型
auto a0 = std::make_any<std::string>("Hello, std::any!\n"
);
std::cout << std::any_cast<std::string&>(a0);
std::cout << a0.type().name() << "\"\n";// 将 lambda 放入 std::any 使用 make_any
auto a_lambda = std::make_any<std::function<void(void)>>([] { std::cout << "Lambda #2.\n"; }
);
std::any_cast<std::function<void(void)>>(a_lambda)();
定义如下
template< class T, class... Args >
std::any make_any( Args&&... args );
// 等价于
return std::any(std::in_place_type<T>, std::forward<Args>(args)...);template< class T, class U, class... Args >
std::any make_any( std::initializer_list<U> il, Args&&... args );
// 等价于
return std::any(std::in_place_type<T>, std::initializer_list<U> il, std::forward<Args>(args)...);
操作
查询
成员函数 | 用途 | 返回值 |
---|---|---|
has_value() | 查询对象是否含有值 | 含值 true,不含值false |
type() | 查询所含类型 | 含值 typeid ,不含值 typeid(void) |
通过使用成员函数 type(),可以检查内含值的类型和某一个类型的 ID 是否相同。如果对象是空的,类
型 ID 将等于 typeid(void)。
std::any m_any_a = "hello"; // type()是const char*
if (m_any_a.type() == typeid(const char*)) { // truestd::cout << std::any_cast<const char*>(m_any_a);
} else if (m_any_a.type() == typeid(std::string)) {std::cout << std::any_cast<std::string>(m_any_a);std::cout << (m_any_a.has_value() ? std::any_cast<std::string>(a) : std::string("NIL"));
} else {// 若都不匹配std::cout << (m_any_a.type() == typeid(void));
}
访问 any_cast
要访问any包含的值,必须使用std::any_cast<>将any转换为真正的类型。
操作 | 含义 |
---|---|
T any_cast<T>(any& operand ) | 将当前值转换为类型 T的值(如果类型不正确将抛出异常) |
T* any_cast(any* operand ) | 将当前值转换为类型 T的指针(如果类型不正确将返回 nullptr) |
any值/引用
存在以下三个重载,支持以各种值类型返回。
// 存在以下三个重载
template< class T >
T any_cast( any operand ); // 返回 值的拷贝,会创建临时对象
template< class T >
T any_cast( any& operand ); // 更推荐转换为引用类型来避免创建临时对象
template< class T >
T any_cast( const any& operand ); // 常引用
// 不直接支持移动语义
std::string s("hello, world!");
// s = std::any_cast<std::string&&>(m_any_a); // 编译期error
std::any m_any_a = std::make_any<std::string>("Hello");
// 1.创建并返回临时对象
auto val = std::any_cast<std::string>(m_any_a);
// 2.返回引用(更推荐)
// - 避免了创建临时对象
auto ref = std::any_cast<std::string&>(m_any_a);
// - 可以修改该值
ref = "world"s;
// 3.返回常引用
// - 避免了创建临时对象
// - 可以避免误修改
auto cref = std::any_cast<const std::string&>(a); // read-access by reference// 支持移动语义
std::string s("hello, world!");
// s = std::any_cast<std::string&&>(m_any_a); // 编译期error
s = std::any_cast<std::string>(std::move(m_any_a)); // 可以使用
s = std::move(std::any_cast<std::string&>(a)); // 或者
如果转换失败,将抛出 std::bad_any_cast
异常。因此,在不缺点当前类型的情况下,最好捕获一下异常。
try {auto s = std::any_cast<std::string>(a);...
}
catch (std::bad_any_cast& e) {std::cerr << "EXCEPTION: " << e.what() << '\n';
}
any指针
为了避免异常处理,可以传递 any对象的地址。即:
为std::any对象的地址调用std::any_cast
template< class T >
const T* any_cast(const any* operand) noexcept;
template< class T >
T* any_cast( any* operand ) noexcept;
类型是否匹配
-
匹配:返回相应的地址指针
-
不匹配:返回
nullptr
注意你使用 if-else链,而不是 switch语句。
// 返回指针
auto p = std::any_cast<std::string>(&m_any_a);
if (p) {}
// 使用新的带初始化的 if语句:
if (auto p = std::any_cast<std::string >(&m_any_a); p != nullptr) {}
if (auto p = std::any_cast<std::string >(&m_any_a)) {}
std::vector<std::any> v;
v.push_back(42);
std::string s = "hello";
v.push_back(s);
for (const auto& a : v) {if (auto pa = std::any_cast<const std::string >(&a)) {std::cout << "string: " << *pa << '\n';} else if (auto pa = std::any_cast<const int>(&a); pa != nullptr) {std::cout << "int: " << *pa << '\n';} else {std::cout << "unkown type\n";}
}
修改
操作 | 含义 |
---|---|
emplace<T>() | 赋予一个类型 T的新值 |
reset() | 销毁 any类型(使对象变为空) |
swap() | 交换两个对象的值 |
std::any m_any_a;
m_any_a = 42; // 含有int类型的值
m_any_a = "hello"; // 含有const char*类型的值
m_any_a.emplace<std::string>("hello"); // 含有std::string类型的值
m_any_a.emplace<std::complex<double>>(4.4, 5.5); // a含 有std::complex<double>类 型 的 值
m_any_a.reset(); // 清空对象
m_any_a = std::any{};
m_any_a = {};