有时我们会需要将一个变量保存在一个未知类型中。对于这样的变量,我们通常会对其进行检查,以确保其是否包含一些信息,如果是包括,那我们将会去判别所包含的内容。以上的所有操作,都需要在一个类型安全的方法中进行。
以前,我们会将可变对象存与void*
指针当中。void类型的指针无法告诉我们其所指向的对象类型,所以我们需要将其进行手动转换成我们期望的类型。这样的代码看起来很诡异,并且不安全。
C++17在STL中添加了一个新的类型——std::any
。其设计就是用来持有任意类型的变量,并且能提供类型的安全检查和安全访问。
本节中,我们将会来感受一下这种工具类型。
我们将实现一个函数,这个函数能够打印所有东西。其就使用std::any
作为参数:
-
包含必要的头文件,并声明所使用的命名空间:
#include <iostream> #include <iomanip> #include <list> #include <any> #include <iterator> using namespace std;
-
为了减少后续代码中尖括号中的类型数量,我们对
list<int>
进行了别名处理:using int_list = list<int>;
-
让我们实现一个可以打印任何东西的函数。其确定能打印任意类型,并以
std::any
作为其参数:void print_anything(const std::any &a) {
-
首先,要做的事就是对传入的参数进行检查,确定参数中是否包含任何东西,还是只是一个空实例。如果为空,那就没有必要再进行接下来的打印了:
if (!a.has_value()) { cout << "Nothing.\n";
-
当非空时,就要需要对其进行类型比较,直至匹配到对应类型。这里第一个类型为
string
,当传入的参数是一个string
,我们可以使用std::any_cast
将a
转化成一个string
类型的引用,然后对其进行打印。我们将双引号当做打印字符串的修饰:} else if (a.type() == typeid(string)) { cout << "It's a string: " << quoted(any_cast<const string&>(a)) << '\n';
-
当其不是
string
类型时,其也可能是一个int
类型。当与之匹配是使用any_cast<int>
将a
转换成int
型数值:} else if (a.type() == typeid(int)) { cout << "It's an integer: " << any_cast<int>(a) << '\n';
-
std::any
并不只对string
和int
有效。我们将map
或list
,或是更加复杂的数据结构放入一个any
变量中。让我们输入一个整数列表看看,按照我们的预期,函数也将会打印出相应的列表:} else if (a.type() == typeid(int_list)) { const auto &l (any_cast<const int_list&>(a)); cout << "It's a list: "; copy(begin(l), end(l), ostream_iterator<int>{cout, ", "}); cout << '\n';
-
如果没有类型能与之匹配,那就不会进行猜测了。我们会放弃对类型进行匹配,然后告诉使用者,我们对输入毫无办法:
} else { cout << "Can't handle this item.\n"; } }
-
主函数中,我们能够对调用函数传入任何类型的值。我们可以使用大括号对来构建一个空的
any
变量,或是直接输入字符串“abc”,或是一个整数。因为std::any
可以由任何类型隐式转换而成,这里并没有语法上的开销。我们也可以直接构造一个列表,然后丢入函数中:int main() { print_anything({}); print_anything("abc"s); print_anything(123); print_anything(int_list{1, 2, 3});
-
当我们想要传入的参数比较大,那么拷贝到
any
变量中就会花费很长的时间,这是可以使用立即构造的方式。in_place_type_t<int_list>{}
表示一个空的对象,对于any
来说其就能够知道应该如何去构建对象了。第二个参数为{1,2,3}
其为一个初始化列表,其会用来初始化int_list
对象,然后被转换成any
变量。这样,我们就避免了不必要的拷贝和移动:print_anything(any(in_place_type_t<int_list>{}, {1, 2, 3})); }
-
编译并运行程序,我们将得到如下的输入出:
$ ./any Nothing. It's a string: "abc" It's an integer: 123 It's a list: 1, 2, 3, It's a list: 1, 2, 3,
std::any
类型与std::optional
类型很类似——具有一个has_value()
成员函数,能告诉我们其是否携带一个值。不过这里,我们还需要对字面的数据进行保存,所以any
要比optional
类型复杂的多。
访问any变量的内容前,我们需要知道其所承载的类型,然后将any
变量转换成那种类型。
这里,使用的比较方式为x.type == typeid(T)
。如果比较结果匹配,那么就使用any_cast
对其内容进行转换。
需要注意的是any_cast<T>(x)
将会返回x
中T
值的副本。如果想要避免对复杂对象不必要的拷贝,那就需要使用any_cast<T&>(x)
。本节的代码中,我们使用引用的方式来获取string
和list<int>
对象的值。
Note:
如果
any
变量转换成为一种错误的类型,其将会抛出std::bad_any_cast
异常。