相关系列文章
C++三剑客之std::variant(二):深入剖析
目录
1.概述
C++17的三剑客分别是std::optional, std::any, std::vairant。今天主要讲std::any。std::any类用于任何可拷贝构造类型的单个值的类型安全容器。在头文件<any>中,c++标准库定义了类std::any。
cpp
namespace std {
class any;
}
从上面的定义可以看出std::any不是模版类,而是一种很特殊的容器,它只能容纳一个元素,但这个元素可以是任意的类型,可以是基本数据类型(int,double,char,float...)也可以是复合数据类型(类、结构体)。
std: any是一种值类型,它能够更改其类型,同时仍然具有类型安全性。也就是说,对象可以保存任意类型的值,但是它们知道当前保存的值是哪种类型。在声明此类型的对象时,不需要指定可能的类型。
2.构建方式
2.1.构造函数
默认情况下,std::any的初始值为空。如:
cpp
std::any a;
也可以赋初值,初始化对象std::any, 如:
cpp
std::any a = 32; //type: int
std::any b = "wegrthweg"; type : const chr*
要保存与初始值类型不同的类型,必须使用in_place_type标记:
cpp
std::any a{std::in_place_type<int>, 420};
std::any b{std::in_place_type<std::string>, "asdfbsrghtr34"};
即使传递给in_place_type的类型也会退化。下面的声明包含一个const char*:
cpp
std::any a{std::in_place_type<const char[6]>, "12345"};
要通过多个参数初始化可选对象,必须创建该对象或将std::in_place_type添加为第一个参数(不能推断包含的类型):
cpp
std::any a1{std::complex{6.0, 2.0}};
std::any a2{std::in_place_type<std::complex<double>>, 6.0, 2.0};
甚至可以传递一个初始化器列表,后面跟着附加的参数:
cpp
auto func = [] (int x, int y) { return std::abs(x) < std::abs(y);};
std::any a{std::in_place_type<std::set<int,decltype(func)>>, {3, 7, -1, -16, 1, 100}, func};
2.2.std::make_any
std::make_any的定义如下:
cpp
//[1]
template< class T, class... Args >
std::any make_any( Args&&... args );
//[2]
template< class T, class U, class... Args >
std::any make_any( std::initializer_list<U> il, Args&&... args );
构造含 T
类型对象的 any
对象,传递提供的参数给 T
的构造函数。
-
等价于 return std::any(std::in_place_type<T>, std::forward<Args>(args)...);
-
等价于 return std::any(std::in_place_type<T>, il, std::forward<Args>(args)...);
std::make_any必须显式指定初始化的类型(如果只传递一个参数,则不会推导出初始化的类型),如:
cpp
#include <any>
#include <complex>
#include <functional>
#include <iostream>
#include <string>
int main()
{
auto a0 = std::make_any<std::string>("Hello, std::any!\n");
auto a1 = std::make_any<std::complex<double>>(0.1, 2.3);
std::cout << std::any_cast<std::string&>(a0);
std::cout << std::any_cast<std::complex<double>&>(a1) << '\n';
using lambda = std::function<void(void)>;
// 把 lambda 放入 std::any。尝试 #1 (失败)。
std::any a2 = [] { std::cout << "Lambda #1.\n"; };
std::cout << "a2.type() = \"" << a2.type().name() << "\"\n";
// any_cast 转型到 <void(void)> 但实际类型不是
// std::function ......,而是 ~ main::{lambda()#1} ,且它对
// 每个 lambda 唯一。所以这会抛出......
try {
std::any_cast<lambda>(a2)();
}
catch (std::bad_any_cast const& ex) {
std::cout << ex.what() << '\n';
}
// 将 lambda 放入 std::any 中。尝试 #2 (成功)
auto a3 = std::make_any<lambda>([] { std::cout << "Lambda #2.\n"; });
std::cout << "a3.type() = \"" << a3.type().name() << "\"\n";
std::any_cast<lambda>(a3)();
}
输出:
cpp
Hello, std::any!
(0.1,2.3)
a2.type() = "Z4mainEUlvE_"
bad any_cast
a3.type() = "St8functionIFvvEE"
Lambda #2.
2.3.operator=分配新值
operator=的定义如下:
cpp
//[1]
any& operator=( const any& rhs );
//[2]
any& operator=( any&& rhs ) noexcept;
//[3]
template<typename ValueType>
any& operator=( ValueType&& rhs );
-
通过复制
rhs
的状态赋值,如同用 any(rhs).swap(*this)。 -
通过移动
rhs
的状态赋值,如同用 any(std::move(rhs)).swap(*this)。赋值后rhs
留在有效但未指定的状态。 -
以
rhs
的类型和值赋值,如同用 any(std::forward<ValueType>(rhs)).swap(*this)。此重载只有在 std::decay_t<ValueType> 与 any 不是同一类型且 std::is_copy_constructible_v<std::decay_t<ValueType>> 为 true 时才会参与重载决议。std::decay_t<ValueType>
必须满足可复制构造条件。示例如下:
cpp
std::any a = 1223;
std::any b = "2222222";
a = b; //[1]
b = 6.34655; //[3]
a = std::make_any<std::complex>(6.0, 2.0); //[2]
上面代码执行a = b调用的是第1个赋值函数,b=6.34655调用的是第3个赋值函数,a = std::make_any<std::complex>(6.0, 2.0) 是调用的第2个赋值函数。
3.访问值std::any_cast
std::any_cast的定义:
cpp
//[1]
template< class T >
T any_cast( const any& operand );
//[2]
template< class T >
T any_cast( any& operand );
//[3]
template< class T >
T any_cast( any&& operand );
//[4]
template< class T >
const T* any_cast( const any* operand ) noexcept;
//[5]
template< class T >
T* any_cast( any* operand ) noexcept;
要访问包含的值,必须使用std::any_cast<>将其转换为其类型。将该值转换为一个字符串,有几个选项:
cpp
std::any_cast<std::string>(a) // yield copy of the value
std::any_cast<std::string&>(a); // write value by reference
std::any_cast<const std::string&>(a); // read-access by reference
在这里,如果转换失败,将抛出std::bad_any_cast异常。因此,在不检查或不知道类型的情况下,最好实现以下功能:
cpp
try {
auto s = std::any_cast<std::string>(a);
...
}
catch (std::bad_any_cast& e) {
std::cerr << "EXCEPTION: " << e.what() << '\n';
}
注意,std::any_cast<>创建了一个传递类型的对象。如果将std::string作为模板参数传递给std::any_cast<>,它将创建一个临时string(一个prvalue),然后用它初始化新对象。如果没有这样的初始化,通常最好转换为引用类型,以避免创建临时对象:
cpp
std::cout << std::any_cast<const std::string&>(a);
要修改该值,需要转换为对应的引用类型:
cpp
std::any_cast<std::string&>(a) = "2134y56";
根据上面的第4或5个转换函数定义还可以为std::any对象的地址调用std::any_cast。在这种情况下,如果类型匹配,则强制转换返回相应的地址指针;如果不匹配,则返回nullptr:
cpp
auto p = std::any_cast<std::string>(&a);
if (p) {
...
}
下面再看一个例子:
cpp
#include <any>
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
int main()
{
// 简单示例
auto a1 = std::any(12);
std::cout << "1) a1 是 int:" << std::any_cast<int>(a1) << '\n';
try
{
auto s = std::any_cast<std::string>(a1); // 抛出
}
catch (const std::bad_any_cast& e)
{
std::cout << "2) " << e.what() << '\n';
}
// 指针示例
if (int* i = std::any_cast<int>(&a1))
std::cout << "3) a1 是 int:" << *i << '\n';
else if (std::string* s = std::any_cast<std::string>(&a1))
std::cout << "3) a1 是 std::string:" << *s << '\n';
else
std::cout << "3) a1 是另一类型,或者没有设置\n";
// 进阶示例
a1 = std::string("hello");
auto& ra = std::any_cast<std::string&>(a1); //< 引用
ra[1] = 'o';
std::cout << "4) a1 是字符串:"
<< std::any_cast<std::string const&>(a1) << '\n'; //< const 引用
auto s1 = std::any_cast<std::string&&>(std::move(a1)); //< 右值引用
// 注意:"s1" 是移动构造的 std::string:
static_assert(std::is_same_v<decltype(s1), std::string>);
// 注意:"a1" 中的 std::string 被置于合法但未指定的状态
std::cout << "5) a1.size():"
<< std::any_cast<std::string>(&a1)->size() //< 指针
<< '\n'
<< "6) s1:" << s1 << '\n';
}
输出:
cpp
1) a1 是 int:12
2) bad any_cast
3) a1 是 int:12
4) a1 是 string:hollo
5) a1.size():0
6) s1:hollo
4.修改器
4.1.emplace
更改所含对象,直接构造新对象,示例如下:
cpp
#include <algorithm>
#include <any>
#include <iostream>
#include <string>
#include <vector>
class Star
{
std::string name;
int id;
public:
Star(std::string name, int id) : name { name }, id { id }
{
std::cout << "Star::Star(string, int)\n";
}
void print() const
{
std::cout << "Star{ \"" << name << "\" : " << id << " };\n";
}
};
auto main() -> int
{
std::any celestial;
// (1) emplace( Args&&... args );
celestial.emplace<Star>("Procyon", 2943);
const auto* star = std::any_cast<Star>(&celestial);
star->print();
std::any av;
// (2) emplace( std::initializer_list<U> il, Args&&... args );
av.emplace<std::vector<char>>({ 'C', '+', '+', '1', '7' } /* 无参数 */ );
std::cout << av.type().name() << '\n';
const auto* va = std::any_cast<std::vector<char>>(&av);
std::for_each(va->cbegin(), va->cend(), [](char const& c) { std::cout << c; });
std::cout << '\n';
}
输出:
cpp
Star::Star(string, int)
Star{ "Procyon" : 2943 };
St6vectorIcSaIcEE
C++17
4.2.reset
销毁所含对象。
4.3.swap
交换两个std::any。
5.观察器
5.1.has_value
检查对象是否含有值,若实例含值则为 true ,否则为 false 。示例如下:
cpp
#include <any>
#include <iostream>
#include <string>
int main()
{
std::boolalpha(std::cout);
std::any a0;
std::cout << "a0.has_value(): " << a0.has_value() << "\n";
std::any a1 = 42;
std::cout << "a1.has_value(): " << a1.has_value() << '\n';
std::cout << "a1 = " << std::any_cast<int>(a1) << '\n';
a1.reset();
std::cout << "a1.has_value(): " << a1.has_value() << '\n';
auto a2 = std::make_any<std::string>("Milky Way");
std::cout << "a2.has_value(): " << a2.has_value() << '\n';
std::cout << "a2 = \"" << std::any_cast<std::string&>(a2) << "\"\n";
a2.reset();
std::cout << "a2.has_value(): " << a2.has_value() << '\n';
}
输出:
cpp
a0.has_value(): false
a1.has_value(): true
a1 = 42
a1.has_value(): false
a2.has_value(): true
a2 = "Milky Way"
a2.has_value(): false
5.2.type
查询所含类型,若实例非空则为所含值的 typeid
,否则为 typeid(void) 。示例如下:
cpp
#include <type_traits>
#include <any>
#include <functional>
#include <iomanip>
#include <iostream>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
#include <vector>
template<class T, class F>
inline std::pair<const std::type_index, std::function<void(std::any const&)>>
to_any_visitor(F const &f)
{
return {
std::type_index(typeid(T)),
[g = f](std::any const &a)
{
if constexpr (std::is_void_v<T>)
g();
else
g(std::any_cast<T const&>(a));
}
};
}
static std::unordered_map<
std::type_index, std::function<void(std::any const&)>>
any_visitor {
to_any_visitor<void>([]{ std::cout << "{}"; }),
to_any_visitor<int>([](int x){ std::cout << x; }),
to_any_visitor<unsigned>([](unsigned x){ std::cout << x; }),
to_any_visitor<float>([](float x){ std::cout << x; }),
to_any_visitor<double>([](double x){ std::cout << x; }),
to_any_visitor<char const*>([](char const *s)
{ std::cout << std::quoted(s); }),
// ......添加更多你的类型的特化......
};
inline void process(const std::any& a)
{
if (const auto it = any_visitor.find(std::type_index(a.type()));
it != any_visitor.cend()) {
it->second(a);
} else {
std::cout << "Unregistered type "<< std::quoted(a.type().name());
}
}
template<class T, class F>
inline void register_any_visitor(F const& f)
{
std::cout << "Register visitor for type "
<< std::quoted(typeid(T).name()) << '\n';
any_visitor.insert(to_any_visitor<T>(f));
}
auto main() -> int
{
std::vector<std::any> va { {}, 42, 123u, 3.14159f, 2.71828, "C++17", };
std::cout << "{ ";
for (const std::any& a : va) {
process(a);
std::cout << ", ";
}
std::cout << "}\n";
process(std::any(0xFULL)); //< 反注册 "y" 的类型( unsigned long long )
std::cout << '\n';
register_any_visitor<unsigned long long>([](auto x) {
std::cout << std::hex << std::showbase << x;
});
process(std::any(0xFULL)); //< OK : 0xf
std::cout << '\n';
}
输出:
cpp
{ {}, 42, 123, 3.14159, 2.71828, "C++17", }
Unregistered type "y"
Register visitor for type "y"
0xf
6.总结
std::any是一个动态类型变量,可以存储任何类型的值。它是由C++17引入的一个新特性。std::any的设计目标是提供一种类型安全且易于使用的方式来在运行时处理各种类型的数据,因为任何错误的类型转换都会在运行时抛出异常。然而,std::any也有一些缺点。首先,因为std::any在运行时并不知道它存储的数据的具体类型,所以我们需要显式地进行类型转换。这可能会使代码变得复杂和难以理解。其次,std::any的性能可能不如其他类型,因为它需要在运行时进行类型检查和类型转换。
总之,只要掌握了这些std::any的特性,明白了它的使用场景,才能灵活的使用std::any。