10.2.5 std::any
C++是一门强类型的语言,意味着一个“数据”的出现,必然要伴随着它有一个确定的类型。体现在STL中,各类容器都要求所存储的数据,它们的类型都必须是一致的。
尽管是这些容器是模板,可以变幻出各种类型的容器,但每一个具体的容器对象,都只能存储一种类型的元素。
解决办法一是在容器里存储指针,然后再new出不同的对象,这其中又有两种做法,一是C语言风格,存储一个“void * ”,再一个是面向对象的风格,设定一个共同的基类,然后存储该基类指针,实际new出的是各种派生类。
在C++ STL容器中直接存储指针,存在两个讨厌的地方,一是要记得最后需要清理这些指针(delete);二是类型不安全。从容器中取出指针时,为了得到它的真实指向,往往需要强制转换。
问题具体化一下:现在有三个变量,把它们都存储在std::list <T>中:
std::string name = "D2"; //一个字符串
int age = 12; //一个整数
std::vector <int> scores; //一个整数的vector
scores.push_back(91);
scores.push_back(92);
看起来要三个不同类型的list来分别存储:list <string>,list <int>,list <vector <int>>
“std::any”可以解决这个问题。any是一个类,它被设计成可以持有各种类型的数据,对外则统统表现成一个类:any。这样一来,只需一个list <any>,就可以存储多种类型的数据了:
以any为元素类型的容器,简直像是吞食一样的鳄鱼。
036行的代码透露了一个“天机”,由于C++在类型转换“会帮一次忙”(当然它只帮一次忙)的功能,所以前面“用any包装”的工作其实时不必要的,可以用 list<any>:
lst.push_back(12)
lst.push_back(name)
lst.push_back(score)
从容器中取出时,数据还是any类型。
面对一个any类型的数据,要得到它原来的类型是什么,这只有靠强大的记忆力了。
跳过上述代码和list有关的操作,直接看一个数据变成any之后,再如何从any变回原身。
这个变回的过程,需要用到 any_cast <T>()操作,它和标准库的static_cast <>()、dynamic_cast<>()等等case操作有一致的界面。
boost :: any any1(std::string("D2")); //可以包装string
boost :: any any2(12); //可以包装整数
boost :: any any3(scores); //连容器都可以再包装
//现出“原形”
std::string s = boost::any_cast <std::string> (any1);
int i = boost::any_cast <int> (any2);
std::vector<int> v = boost::any_cast <std::vector <int>>(any3);
any的转换(转回原类型)发挥了C++强类型的优点,它会帮你检查类型兼容,如果转换出错,就抛出异常,如果不处理这个异常,程序就直接挂掉。
这个异常类型是“std::bad_any_cast”,下面尝试将一个std::list <int> 强制转换成一个int,这也太疯狂了。
【危险】:"any"的危险
一,是如果让any包装一个指针,则它并不会在其自身生命周期结束时,自动释放所持有的指针,这和STL的容器设计理念一致。
二,是从总体设计上讲,通常并不推荐积极使用any,应该只把any当成项目中某个小功能模块里解决某个小问题的一个小工具。
注意:使用std::any,需要支持C++17标准的编译器,否则可以考虑使用boost::any。