道德三皇五帝,功名夏侯商周。五霸七雄闹春秋,顷刻兴亡过手。青石几行名姓,北邙无数荒丘。前人播种后人收,说甚龙争虎斗。
1. 为什么需要动态类型推断(RTTI)
大家都说 RTTI 是面向对象的一个重要特征,它是伴随多态而形成的。所以没有多态就没有动态类型推断的意义。那么为什么多态就需要 RTTI 呢?我们考虑一个正遇到的场景:我在前端是如何知道一个 graphItem是反应器还是流的呢?我就使用了非常弱智的做法:我给每个 graphItem分配三个指针,如果 graphItem是 CSTR,那么我就让它的 backCSTR指针不是nullptr。通过判断指针是否为空来判断到底这个对象代表的是反应器还是流。这个做法显然不是很优美,但是反映了一个广大人民群众的深刻需求:我们有时候需要在运行的时候知道一个对象到底是什么类型的。
那一个对象的类型难道不是编译阶段就一定会确定下来的吗?如果有多态在的话,那就不是了。比如上述场景,我应该给graphItem安排一个BaseObject指针作为成员变量,因为每个graphItem只可能对应一个BaseObject对象。但是BaseObject是一个抽象类,它会被具体化为CSTR或者是BR,这个时候我就需要判断一个基类指针指向的对象到底是个什么类型了。
2. 如何做到动态类型推断?
这里我们需要注意,并不是所有的平台和编译器都支持 RTTI,这个需要看情况。如果它支持的话,那么我们需要包含头文件:<typeinfo>。然后调用一个函数typeid即可。这个函数不仅可以动态推断,它也能判断静态的类型,比如:
double d = 1.0;cout << typeid(d) == typeid(1.1) << endl;cout << typeid(d) == typeid(double) << endl;
这两种写法都是可以的。这里需要注意的是,`typeid(int)`不一定得到的结果就是 "int",这个和编译器有关,但是可以保证`typeid(1)`和`typeid(int)`一定是相等的。比如 gcc 规定 typeid(int) = "i" 。<br />这里有一个注意事项:**If **_**p**_** is a pointer, then typeid(p) determines the type of this pointer, not of the object it points to.**
这句话是说,如果我们用typeid去判断一个指针的话,那么判断出来的是这个指针的类型而不是它指向的对象的真实类型。所以我们应该用typeid(*p)。
还有一点,这个推断也可以用来推断引用的类型,比如:
Building build;Station sta1,sta2;Station* p_sta1 = &sta1;Building* p_sta2 = &sta2;Building& r_sta1 = sta1;cout << " build: " << typeid(build).name() << endl<< " sta1: " << typeid(sta1).name() << endl<< " sta2: " << typeid(sta2).name() << endl<< " p_sta1: " << typeid(p_sta1).name() << endl<< " p_sta2: " << typeid(p_sta2).name() << endl<< "*p_sta1: " << typeid(*p_sta1).name() << endl<< "*p_sta2: " << typeid(*p_sta2).name() << endl<< " r_sta1: " << typeid(r_sta1).name() << endl;/*build: 8Buildingsta1: 7Stationsta2: 7Stationp_sta1: P7Stationp_sta2: P8Building*p_sta1: 7Station*p_sta2: 7Stationr_sta1: 7Station*/
3. dynamic_cast
我小时候就被家长教导,类型转换尽量使用static_cast。现在我又学会了一个新的东西:dynamic_cast。它的意义在于,有的时候我们希望能够把一个基类指针转换成一个派生类的指针,这是非常自然的事情,那么动态转化就可以实现这个过程,而且它非常的安全。
它的安全在于这个机制:如果被转化的类型是一个指针,那么如果转化失败(也就是说程序发现指针指向的对象并不是想转化过去的派生类的类型,比如指针是 A,派生类有 B 和 C,那么你把一个指向 B 类型的 A指针转化成 C 就是不合理的),它会返回一个空指针。这就让人感到非常的安全。不过安全往往意味着效率方面的代价。
如果被转化的类型是一个引用,而且转化失败了,怎么办呢?引用是不存在空引用这个概念的。所以我们会抛出一个异常:**bad_cast**。
