C++ RTTI机制解析 c&rt算法
RTTI概念
RTTI(Run Time Type Identification)即通过运行时类型识别,程序能够使用基类的指针或引用来检查这些指针或引用所指向的对象的实际派生类型。面向对象的程序编程语言C++、Java、Delphi都提供RTTI的支持。为什么会出现RTTI机制?其原因是,C++是一种静态类型语言,其数据类型是在编译期就确定的,不能在运行时更改。然而由于面向对象程序设计中多态性的要求,C++中的指针或引用本身的类型,可能与它实际代表(指向或引用)的类型并不一致。有时需要将一个多态指针转换为其实际指向对象的类型,就需要知道运行时的类型信息,因此就产生了运行时对类型识别的要求。
typeid和dynamic_cast操作符
【RTTI提供两个操作符】
- typeid操作符:返回指针和应用的实际类型。比如typeid(A).name既可以知道对象A是什么类型。
- dynamic_cast操作符:将基类的指针或引用安全地转换为派生类的指针或引用。在C++中由于虚函数的存在,也就会产生多态性;对于多态性的对象,在程序编译时可能会出现无法确定对象类型的情况,类型的确定要在运行时利用运行时类型标识做出;此时也就需要使用typeid函数。
【typeid函数实例】
#include <iostream>
#include <typeinfo>
using namespace std;
struct DATA
{
};
class Base
{
public:
virtual void Print() { cout << "This is class Base." << endl; }
};
class Derived : public Base
{
public:
void Print() { cout << "This is class Derived." << endl; }
};
int main()
{
short s = 2;
unsigned ui = 10;
int i = 10;
char ch = 'a';
wchar_t wch = L'b';
float f = 1.0f;
double d = 2;
cout << typeid(s).name() << endl; // short
cout << typeid(ui).name() << endl; // unsigned int
cout << typeid(i).name() << endl; // int
cout << typeid(ch).name() << endl; // char
cout << typeid(wch).name() << endl; // wchar_t
cout << typeid(f).name() << endl; // float
cout << typeid(d).name() << endl; // double
DATA* d1 = new DATA();
DATA d2;
Base* b1 = new Base();
Base b2;
Derived* v1 = new Derived();
Derived v2;
Base* v3 = new Derived();
cout << typeid(d1).name() << endl;// struct DATA *
cout << typeid(d2).name() << endl;// struct DATA
cout << typeid(b1).name() << endl;// class Base *
cout << typeid(b2).name() << endl;// class Base
cout << typeid(v1).name() << endl;// class Derived *
cout << typeid(v2).name() << endl;// class Derived
cout << typeid(v3).name() << endl;// class Base *
return 0;
}
【运行结果】
从代码中看出,typeid函数支持自定义结构体和类,都是通过调用name函数获取对象的类型。
【type_info类】
其实,typeid是一个返回类型为type_info类型的函数,在Visual Studio 2012中查看type_info类的定义如下:
class type_info
{
public:
virtual ~type_info();
bool operator==(const type_info& _Rhs) const; // 用于比较两个对象的类型是否相等
bool operator!=(const type_info& _Rhs) const; // 用于比较两个对象的类型是否不相等
bool before(const type_info& _Rhs) const;
// 返回对象的类型名字,这个函数用的很多
const char* name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;
const char* raw_name() const;
private:
void* _M_data;
char _M_d_name[1];
type_info(const type_info& _Rhs);
type_info& operator=(const type_info& _Rhs);
static const char* _Name_base(const type_info*, __type_info_node* __ptype_info_node);
static void _Type_info_dtor(type_info*);
};
在type_info类中,复制构造函数和赋值运算符都是私有的,同时也没有默认的构造函数。
【RTTI核心】
我们先来看一段代码:
#include <iostream>
#include <typeinfo>
using namespace std;
class A1
{
public:
void print() { cout << "This is class A1" << endl; }
};
class A2 : public A1
{
public:
void print() { cout << "This is class A2" << endl; }
};
class B1
{
public:
virtual void print() { cout << "This is class B1" << endl; }
};
class B2 : public B1
{
public:
void print() { cout << "This is class B2" << endl; }
};
int main()
{
A1* a1 = new A2();
B1* b1 = new B2();
cout << typeid(a1).name() << endl; // class A1 *
cout << typeid(*a1).name() << endl;// class A1
cout << typeid(b1).name() << endl; // class B1 *
cout << typeid(*b1).name() << endl;// class B2
}
从代码中可以看出, a1实际指向是A2,为什么得到的是class A1呢?然而,把print函数变成虚函数,typeid(*b1).name()为什么得到的是class B2呢?其原因是:
- 当类中不存在虚函数时,typeid指出操作数的静态类型,即编译时的类型。静态类型在程序的运行过程中并不会改变,所以并不需要在程序运行时计算类型。
- 当类中存在虚函数时,typeid需要在程序运行时计算类型,因为其操作数的类型在编译期是不能确定的。
【type_info类比较运算符】
使用type_info类中重载的==和!=比较两个对象的类型是否相等
这个会经常用到,通常用于比较两个带有虚函数的类的对象是否相等,例如以下代码:
#include <iostream>
#include <typeinfo>
using namespace std;
class A
{
public:
virtual void Print() { cout << "This is class A." << endl; }
};
class B : public A
{
public:
void Print() { cout << "This is class B." << endl; }
};
class C : public A
{
public:
void Print() { cout << "This is class C." << endl; }
};
void Compare(A* a)
{
if (typeid(*a) == typeid(A))
{
cout << "is A." << endl;
}
else if (typeid(*a) == typeid(B))
{
cout << "is B." << endl;
}
else if (typeid(*a) == typeid(C))
{
cout << "is C." << endl;
}
else
{
cout << "other" << endl;
}
}
int main()
{
A* p1 = new A();
A* p2 = new B();
A* p3 = new C();
Compare(p1);
Compare(p2);
Compare(p3);
delete p1;
delete p2;
delete p3;
return 0;
}
【dynamic_cast机制】
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。在多态类型之间的转换主要使用dynamic_cast,因为类型提供了运行时信息。
举个例子:
#include <iostream>
#include <typeinfo>
using namespace std;
class A
{
public:
virtual void Print() { cout << "This is class A." << endl; }
};
class B
{
public:
virtual void Print() { cout << "This is class B." << endl; }
};
class C : public A, public B
{
public:
void Print() { cout << "This is class C." << endl; }
};
int main()
{
A* pA = new C();
//C *pC = pA; // Wrong 编译器会提示错误
C* pC = dynamic_cast<C*>(pA);
if (pC != NULL)
{
pC->Print();
}
delete pA;
}
dynamic_cast主要用于在多态的时候,它允许在运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转换类型,把基类指针(引用)转换为派生类指针(引用)。
当类中存在虚函数时,编译器就会在类的成员变量中添加一个指向虚函数表的vptr指针,每一个class所关联的type_info object也经由virtual table被指出来,通常这个type_info object放在表格的第一个slot。当我们进行dynamic_cast时,编译器会帮我们进行语法检查。如果指针的静态类型和目标类型相同,那么就什么事情都不做;否则,首先对指针进行调整,使得它指向vftable,并将其和调整之后的指针、调整的偏移量、静态类型以及目标类型传递给内部函数。其中最后一个参数指明转换的是指针还是引用。两者唯一的区别是,如果转换失败,前者返回NULL,后者抛出bad_cast异常。