将void*替换为更为安全的std::any
有时我们会需要将一个变量保存在一个未知类型中。对于这样的变量,我们通常会对其进行检查,以确保其是否包含一些信息,如果是包括,那我们将会去判别所包含的内容。以上的所有操作,都需要在一个类型安全的方法中进行。
以前,我们会将可变对象存与void*
指针当中。void类型的指针无法告诉我们其所指向的对象类型,所以我们需要将其进行手动转换成我们期望的类型。这样的代码看起来很诡异,并且不安全。
C++17在STL中添加了一个新的类型——std::any
。其设计就是用来持有任意类型的变量,并且能提供类型的安全检查和安全访问。
本节中,我们将会来感受一下这种工具类型。
How to do it...
我们将实现一个函数,这个函数能够打印所有东西。其就使用std::any
作为参数:
包含必要的头文件,并声明所使用的命名空间:
为了减少后续代码中尖括号中的类型数量,我们对
list<int>
进行了别名处理:让我们实现一个可以打印任何东西的函数。其确定能打印任意类型,并以
std::any
作为其参数:首先,要做的事就是对传入的参数进行检查,确定参数中是否包含任何东西,还是只是一个空实例。如果为空,那就没有必要再进行接下来的打印了:
当非空时,就要需要对其进行类型比较,直至匹配到对应类型。这里第一个类型为
string
,当传入的参数是一个string
,我们可以使用std::any_cast
将a
转化成一个string
类型的引用,然后对其进行打印。我们将双引号当做打印字符串的修饰:当其不是
string
类型时,其也可能是一个int
类型。当与之匹配是使用any_cast<int>
将a
转换成int
型数值:std::any
并不只对string
和int
有效。我们将map
或list
,或是更加复杂的数据结构放入一个any
变量中。让我们输入一个整数列表看看,按照我们的预期,函数也将会打印出相应的列表:如果没有类型能与之匹配,那就不会进行猜测了。我们会放弃对类型进行匹配,然后告诉使用者,我们对输入毫无办法:
主函数中,我们能够对调用函数传入任何类型的值。我们可以使用大括号对来构建一个空的
any
变量,或是直接输入字符串“abc”,或是一个整数。因为std::any
可以由任何类型隐式转换而成,这里并没有语法上的开销。我们也可以直接构造一个列表,然后丢入函数中:当我们想要传入的参数比较大,那么拷贝到
any
变量中就会花费很长的时间,这是可以使用立即构造的方式。in_place_type_t<int_list>{}
表示一个空的对象,对于any
来说其就能够知道应该如何去构建对象了。第二个参数为{1,2,3}
其为一个初始化列表,其会用来初始化int_list
对象,然后被转换成any
变量。这样,我们就避免了不必要的拷贝和移动:编译并运行程序,我们将得到如下的输入出:
How it works...
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
异常。
Last updated