整理本系列笔记的最后一篇,关于运行时类型信息(RTTI)。
保证安全的向下转型
向下类型转换:是指由父类向子类的类型转换,由于父类信息少于子类,所以通常来说,没有特殊机制支持的话,这种转换是不安全的,要支持此机制的话,在空间和时间上都需要额外的负担:
1. 空间上,需要额外的空间存储类型信息,通常是一个指针,指向某个类型信息节点。
2. 时间上,需要额外的时间,已决定执行期的类型。
一种主流的策略是通过经由虚函数的声明来区别class,通过将指向类相关的运行时信息的指针存入虚函数表,会大大降低额外负担。
保证安全的动态转型
也就是dynamic_cast<>表现出来的行为:
- 如果向下转型是安全的,则返回适当的转换类型。
- 如果不安全,则返回空。
对于如下代码:
Base* pb = new Derived;
Derived* pd = dynamic_cast<Derived*>(pb);
对于dynamic_cast<>, 其成本来自于:
1. Devired的一个类型描述器会被编译器产生出来。
2. b指向的类对象的类型描述器要在运行期通过虚表指针取得,像下面这样:
((type_info*)(pb->vptr[0]))->_type_descriptor;
两个类型描述器需要进行比较,可能会交给一个库函数,无论如何,这显然比static_cast<>要昂贵的多,但也安全得多。
引用并不是指针
当对于一个指针施以dynamic_cast<>时,可能产生两种结果:
1. 传回真正的地址,这表示在保证安全的前提下,转换成功了,这个对象的动态类型被确认了,一些与类型有关的操作现在可以施行于其上了。
2. 传回0,表示没有指向任何对象,表示无法安全转型。
第二点对于引而言,并不适用,因为,引用不可以指向空,引用是一有对象的别名,如果一个引用可以设为0,则表示0被隐式的转换为某种类型的临时对象,引用成为该临时对象的一个别名(alias)。
所以当dynamic_cast<>运算符施行于引用是,不能提供等同于指针情况的返回非空或者空,而是发生如下事情:
1. 如果可以安全转型,则与指针情况类似。
2. 如果引用并不是真正是某一种子类,由于不能传回0,所以抛出bad_cast异常。
例如:针对指针我可以这样定义一个转换处理函数:
simplify_conv_op(Base* pb)
{
Derived* pd = dynamic_cast<Derived*>(pb)
if(pd)
{
//...process pd
}
else
{
//pd is null which means cast fails.
}
}
而针对引用,则不能通过空与非空来判断:
simplify_conv_op_ref(const Base& b)
{
try {
Derived& d = dynamic_cast<Derived&>(b)
//...process d
}
catch (bad_cast)
{
//catch bad_cast exception which means cast fails.
}
}
Typeid运算符
通过typeid可以通过一个引用提前判断类型信息,已决定是否进行动态类型转化:
simplify_conv_op_ref(const Base& b)
{
if(typeid(b) == typeid(Derived))
{
Derived& d = dynamic_cast<Derived&>(b)
//...process d
}
else()
{
//...
}
}
typeid运算符返回一个类型为type_info的常量引用(const reference),type_info类的可能的声明如下:
class type_info {
public:
virtual ~type_info();
bool operator==(const type_info&) const;
bool operator!=(const type_info&) const;
bool before(const type_info&) const;
const char* name() const;//original class name
private:
//prevent memberwise init and copy
type_info(const type_info&);
type_info& operator=(const type_info&);
//data member
};
整个关于C++对象模型的笔记全部结束了,算上本篇总共19篇,林林总总也是不少内容,希望可以对同行们有所帮助,当然,重要的是给自己一个Back-up,以便之后restore之用。