相关系列文章
目录
1.概述
C++17的三剑客分别是std::optional, std::any, std::vairant。今天主要讲std::any。std::any类用于任何可拷贝构造类型的单个值的类型安全容器。在头文件
- namespace std {
- class any;
- }
从上面的定义可以看出std::any不是模版类,而是一种很特殊的容器,它只能容纳一个元素,但这个元素可以是任意的类型,可以是基本数据类型(int,double,char,float...)也可以是复合数据类型(类、结构体)。
std: any是一种值类型,它能够更改其类型,同时仍然具有类型安全性。也就是说,对象可以保存任意类型的值,但是它们知道当前保存的值是哪种类型。在声明此类型的对象时,不需要指定可能的类型。
2.构建方式
2.1.构造函数
默认情况下,std::any的初始值为空。如:
std::any a;
也可以赋初值,初始化对象std::any, 如:
- std::any a = 32; //type: int
- std::any b = "wegrthweg"; type : const chr*
要保存与初始值类型不同的类型,必须使用in_place_type标记:
- std::any a{std::in_place_type<int>, 420};
- std::any b{std::in_place_type
, "asdfbsrghtr34"};
即使传递给in_place_type的类型也会退化。下面的声明包含一个const char*:
std::any a{std::in_place_type<const char[6]>, "12345"};
要通过多个参数初始化可选对象,必须创建该对象或将std::in_place_type添加为第一个参数(不能推断包含的类型):
- std::any a1{std::complex{6.0, 2.0}};
- std::any a2{std::in_place_type
double>>, 6.0, 2.0};
甚至可以传递一个初始化器列表,后面跟着附加的参数:
- auto func = [] (int x, int y) { return std::abs(x) < std::abs(y);};
-
- std::any a{std::in_place_type
int,decltype(func)>>, {3, 7, -1, -16, 1, 100}, func};
2.2.std::make_any
std::make_any的定义如下:
- //[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 il, Args&&... args );
构造含 T
类型对象的 any
对象,传递提供的参数给 T
的构造函数。
1) 等价于 return std::any(std::in_place_type
2) 等价于 return std::any(std::in_place_type
std::make_any必须显式指定初始化的类型(如果只传递一个参数,则不会推导出初始化的类型),如:
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- auto a0 = std::make_any
("Hello, std::any!\n"); - auto a1 = std::make_any
double>>(0.1, 2.3); -
- std::cout << std::any_cast
(a0); - std::cout << std::any_cast
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 转型到
但实际类型不是 - // std::function ……,而是 ~ main::{lambda()#1} ,且它对
- // 每个 lambda 唯一。所以这会抛出……
- try {
- std::any_cast
(a2)(); - }
- catch (std::bad_any_cast const& ex) {
- std::cout << ex.what() << '\n';
- }
-
- // 将 lambda 放入 std::any 中。尝试 #2 (成功)
- auto a3 = std::make_any
([] { std::cout << "Lambda #2.\n"; }); - std::cout << "a3.type() = \"" << a3.type().name() << "\"\n";
- std::any_cast
(a3)(); - }
输出:
- Hello, std::any!
- (0.1,2.3)
- a2.type() = "Z4mainEUlvE_"
- bad any_cast
- a3.type() = "St8functionIFvvEE"
- Lambda #2.
2.3.operator=分配新值
operator=的定义如下:
- //[1]
- any& operator=( const any& rhs );
- //[2]
- any& operator=( any&& rhs ) noexcept;
- //[3]
- template<typename ValueType>
- any& operator=( ValueType&& rhs );
1) 通过复制 rhs
的状态赋值,如同用 any(rhs).swap(*this)。
2) 通过移动 rhs
的状态赋值,如同用 any(std::move(rhs)).swap(*this)。赋值后 rhs
留在有效但未指定的状态。
3) 以 rhs
的类型和值赋值,如同用 any(std::forwardstd::decay_t
必须满足可复制构造条件。示例如下:
- std::any a = 1223;
- std::any b = "2222222";
- a = b; //[1]
- b = 6.34655; //[3]
- a = std::make_any
(6.0, 2.0); //[2]
上面代码执行a = b调用的是第1个赋值函数,b=6.34655调用的是第3个赋值函数,a = std::make_any
3.访问值std::any_cast
std::any_cast的定义:
- //[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<>将其转换为其类型。将该值转换为一个字符串,有几个选项:
- std::any_cast
(a) // yield copy of the value - std::any_cast
(a); // write value by reference - std::any_cast<const std::string&>(a); // read-access by reference
在这里,如果转换失败,将抛出std::bad_any_cast异常。因此,在不检查或不知道类型的情况下,最好实现以下功能:
- try {
- auto s = std::any_cast
(a); - ...
- }
- catch (std::bad_any_cast& e) {
- std::cerr << "EXCEPTION: " << e.what() << '\n';
- }
注意,std::any_cast<>创建了一个传递类型的对象。如果将std::string作为模板参数传递给std::any_cast<>,它将创建一个临时string(一个prvalue),然后用它初始化新对象。如果没有这样的初始化,通常最好转换为引用类型,以避免创建临时对象:
std::cout << std::any_cast<const std::string&>(a);
要修改该值,需要转换为对应的引用类型:
std::any_cast(a) = "2134y56";
根据上面的第4或5个转换函数定义还可以为std::any对象的地址调用std::any_cast。在这种情况下,如果类型匹配,则强制转换返回相应的地址指针;如果不匹配,则返回nullptr:
- auto p = std::any_cast
(&a); - if (p) {
- ...
- }
下面再看一个例子:
- #include
- #include
- #include
- #include
- #include
-
- int main()
- {
- // 简单示例
- auto a1 = std::any(12);
- std::cout << "1) a1 是 int:" << std::any_cast<int>(a1) << '\n';
-
- try
- {
- auto s = std::any_cast
(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
(&a1)) - std::cout << "3) a1 是 std::string:" << *s << '\n';
- else
- std::cout << "3) a1 是另一类型,或者没有设置\n";
-
- // 进阶示例
- a1 = std::string("hello");
- auto& ra = std::any_cast
(a1); //< 引用 - ra[1] = 'o';
-
- std::cout << "4) a1 是字符串:"
- << std::any_cast
const&>(a1) << '\n'; //< const 引用 -
- auto s1 = std::any_cast
(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
(&a1)->size() //< 指针 - << '\n'
- << "6) s1:" << s1 << '\n';
- }
输出:
- 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
更改所含对象,直接构造新对象,示例如下:
- #include
- #include
- #include
- #include
- #include
-
- 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
("Procyon", 2943); - const auto* star = std::any_cast
(&celestial); - star->print();
-
- std::any av;
- // (2) emplace( std::initializer_list il, Args&&... args );
- av.emplace
char>>({ 'C', '+', '+', '1', '7' } /* 无参数 */ ); - std::cout << av.type().name() << '\n';
- const auto* va = std::any_cast
char>>(&av); - std::for_each(va->cbegin(), va->cend(), [](char const& c) { std::cout << c; });
- std::cout << '\n';
- }
输出:
- 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 。示例如下:
- #include
- #include
- #include
-
- 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
("Milky Way"); - std::cout << "a2.has_value(): " << a2.has_value() << '\n';
- std::cout << "a2 = \"" << std::any_cast
(a2) << "\"\n"; - a2.reset();
- std::cout << "a2.has_value(): " << a2.has_value() << '\n';
- }
输出:
- 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) 。示例如下:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- 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
) - g();
- else
- g(std::any_cast
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
(f)); - }
-
- auto main() -> int
- {
- std::vector
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';
- }
输出:
- { {}, 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。
参考:std::any - cppreference.com
评论记录:
回复评论: