C++ RTTI

运行时类型识别(run-time type identification)

通过RTTI,程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类

提供RTTI操作 - 1) typeid操作符 , 返回指针或引用所指对象的实际类型 - 2) dynamic_cast 操作符, 将基类类型的指针或引用安全地转换为派生类的指针或引用
//这些操作符只为带有一个或多个虚函数的类返回动态类型信息,对于其他类型,则返回静态类型

通常, 从基类指针获得派生类行为的最好方法是通过虚函数,但是某些情况下去不可能使用虚函数 这些情况下,RTTI提供了可选的机制。然而,这种机制比使用虚函数更容易出错:程序员必须知道应该将对象 强制转换为哪种类型,并且必须检查转化是否成功了.

一、dynamic_cast操作符

  • dynamic_cast一起使用的指针必须是有效的–它必须是0或者指向一个对象
  • dynamic_cast 的运行检查
    如果转化到指针类型的dynamic_cast 失败,则dynamic_cast的结果为0
    如果转换到引用类型的dynamic_cast 失败 ,则抛出一个std::bad_cast类型的异常

因此,dynamic_cast 操作符一次执行链两个操作

首先:验证被请求的转换是否有效
其次:转换 ### 使用dynamic_cast操作符 ###

1
2
3
4
5
6
class Base{};
class Derived :public Base{};
Base *basePtr;
if(Derived *DPtr = dynamic_cast<Derived*> basePtr)
{}
else {}

说明:在运行时,如果basePtr实际指向的是Derived,那么转换成功,并且将DPtr初始化为指向basePtr 所指的Derived对象
否则,转换结果是0,意味着Dptr置为0,并在if() 中失败

//当然,在使用dynamic_cast时,经常会连同if() 语句一起使用……好处多多哦^_^

使用dynamic_cast 和引用类型

操作形式:

1
dynamic_cast<type &> (val)

这里,Type是目标类型,val是基类类型的对象

注意:只有当val实际引用一个Type类型对象,或者val是一个Type派生类的对象的时候,dynamic_cast操作才将操作数val转换为想要的Type类型

//上面的说法似乎有些矛盾(反正我觉得有), 其实,简单这样理解就行:一种类型要转换为另一种类型,自然是要包括目的类型的
比如:

1
2
B *pb = new B;
C &p = dynamic_cast<C&> pb;

要想让转换成功,B中必须有C,那要怎么做才能使B中有C呢?当B是C的派生类时 B不就包含了C了嘛!!(当然,如果pb指向的类型与C是同类型的,那自然也 可以转化啦)

好!看你是不是真的理解了,出道题练练!

1
2
3
4
5
6
7
8
9
10
11
12
class A {};
class B :public A{};
class C :public B{};
class D :public B , public A{};
下面哪一项转换失败:
A. A * pa = new C;
B * pb = dynamic_cast<B*> pa;
B. B *pb = new B;
C *pc = dynamic_cast<C*> pb;
C. A *pa = new D;
B *pb = dynamic_cast<B*> pa;

我不会告诉你答案是B的~~~

typeid操作符

typeid操作符是程序能够问一个表达式:你是什么类型的?

表达式形式:

1
2
typeid(e)
e是任意表达式或者类型名

当然,如果表达式的类型是类类型且该类型包含一个或多个虚函数,则表达式的动态类型可能不同于它的静态编译类型

typeid操作符的结果是名为type_info的标准库类型的对象引用,要使用type_info类, 必须包含头文件 typeinfo

使用typeid操作符

typeid 最常见的用途是比较两个表达式的类型,或者将表达式的类型与特定的类型相比较

1
2
3
4
Base *bp;
Derived *pb;
if(typeid(*bp) == typeid(*pb)){}
if(typeid(*bp) == typeid(Derived)){}

第一个if ,如果bp所指的对象与dp所指的对象的实际类型,如果他们指向同一类型,则测试成功,类似地 , 如果bp当前指向Derived对象,则第二个if成功

注意: typeid的操作数是表示对象的表达式—测试*bp,而不是bp(注意传入实参所代表的意义)
例如:

1
if(typeid(bp) == typeid(Derived)){}

这个测试将Base*类型与Derived类型相比较,这两个类型不相等!!

1
2
3
//如果指针p的值为0 , 那么,如果p的类型是带虚函数的类型,则typeid(*p)抛出一个bad_typeid异常
//;如果p的类型没有定义任何虚函数,则结果与p的值是不相关的。正像计算表达式sizeof()一样,编译器不计算*p ,它使用p的静态类型
//这并不要求p本身是有效指针

RTTI的使用

首先引入问题:

考虑一个类层次,我们要为它定义一个相等操作符。 如果两个对象的给定数据成员集合的值相同,他们就相等。每个派生类都可以增加自己的数据成员

我们希望在测试的 时候包含这些数据

因为确定派生类型相等与确定基类类型的相等所考虑的值不同,所以对层次中的每一对类型潜在需要一个不同的相等操作符。而且,希望能够使用给定类型作为左操作数或右操作数,所以实际上对每一对类型将需要两个操作符
例如:如果类层次中只有两个类型,则需要四个函数

1
2
3
4
bool operator=(const Base& , const Base&);
bool operator=(const Derived &, const Base& )
bool operator=(const Base& , const Derived&)
bool operator=(const Derived& , const Derived);

显然有点多了

当然我们会认为定义一个虚函数是个不错的idea , 这些虚函数可以再类层次中每一层执行相等测试。给定这些虚函数 可以定义单个的相等操作符,操作基类类型的引用,该相等操作符可以将工作委派给可以完成实际工作的虚操作

但是,问题来了,麻烦在于决定equal操作的 形参类型。

1.虚函数在基类类型和派生类型中必须有相同的形参类型,这意味着,虚equal操作必须有一个形参是基类的引用。
2.再想想,当两个派生类对象进行比较的时候,我们希望比较可能特定于派生类的数据成员。如果形参是基类的引用, 只能比较派生类中基类的部分,而我们却无法访问派生类中的特定成员

——问题引出,自然得用RTTI来解决啦——–

举例分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base
{
friend bool operator=(const Base& , const Base&);
public:
protected:
virtual bool equal(const Base&) const;
};
class Derived: public Base
{
friend bool operator=(const Base& , const Base&);
public:
protected:
bool equal(const Base&) const;
};

类型敏感的相等操作符

1
2
3
4
bool operator=(const Base &lhs , const Base &rhs)
{
return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}

虚函数equal

1
2
3
4
5
6
7
8
9
bool Derived::equal(const Base& rhs) const
{
if(const Derived *dp =
dynamic_cast<Derived*> &rhs)//不能把原本不是指针的rhs 转换为指针吧,必须&rhs
else
return false;
}
//这个强制转换应该总是成功的--毕竟,只有在测试了两个操作数类型相同之后,才从相等操作符调用该函数。但是
//这个转换时非常必要的,以便函数可以访问右操作数的派生了成员.

基类中的equal函数

1
2
3
bool Base::equal(const Base& rhs) const
{}
//呵呵,其实无需定义的,相等操作符已经把该做的都做了

type_info类

1
2
3
4
t1 == t2
t1 != t2
t.name() 返回C风格的字符串,这是类型名字的可显示版本。类型名字用系统相关的方法产生
t1.before(t2) 返回指出t1是否出现在t2之前的bool值,before强制的次序与编译器有关

因为打算做基类使用 , type_info类也提供公用虚析构函数。如果编译器想要提供附加的类型信息应该在type_info的派生类中进行

默认构造函数和复制构造函数以及赋值操作符都定义为private,所以,不能定义或复制type_info类型的对象。程序中使用type_info对象的唯一方法是使用typeid

个人觉得,比较有用的就是 t.name()
例子就不举了,很简单的

热评文章