RTTI算是C++的一大特性之一了,但也是一个不太好懂的特性。以前一直没有理解清楚RTTI。通过最近的学习,似乎掌握了一点皮毛,这里做一个小总结。首先是《C++编程思想》上的一个例子,由于缺头文件,我把容器改成了vector。
#include <iostream> #include <time.h> #include <typeinfo.h> #include <vector> #include <stdlib.h> //for srand() and rand() using namespace std; class shape { protected: static int count; public: shape() { count++; } virtual ~shape() { count--; } virtual void draw() const = 0; static int quantity() {return count;} }; int shape::count = 0; class rectangle:public shape { void operator=(rectangle&); protected: static int count; public: rectangle() {count++;} ~rectangle() {count--;} rectangle(const rectangle&) {count++;} void draw() const { cout << "rectangle::draw()"<<endl; } static int quantity() {return count;} }; int rectangle::count = 0; class ellipse: public shape { void operator=(ellipse&); protected: static int count; public: ellipse() {count++;} ellipse(const ellipse&) {count++;} ~ellipse() { count--;} void draw() const{ cout << "ellipse::draw()" <<endl; } static int quantity() {return count;} }; int ellipse::count = 0; class circle : public shape { void operator=(circle&); protected: static int count; public: circle() {count++;} circle(const circle&) {count++;} ~circle() {count--;} void draw() const { cout << "circle::draw()" << endl; } static int quanlity() {return count;} }; int circle::count = 0; int main() { vector<shape*> shapes; time_t t; srand((unsigned)time(&t)); const int mod = 12; for(int i = 0;i<rand()%mod; i++) shapes.push_back(new rectangle); for(int j = 0;j<rand() %mod; j++) shapes.push_back(new ellipse); for(int k = 0;k<rand() %mod; k++) shapes.push_back(new circle); int Ncircles = 0; int Nellipses = 0; int Nrects = 0; int Nshapes = 0; for(int u = 0;u<shapes.size();u++) { shapes[u]->draw(); if(dynamic_cast<circle*>(shapes[u])) Ncircles++; if(dynamic_cast<ellipse*>(shapes[u])) Nellipses++; if(dynamic_cast<rectangle*>(shapes[u])) Nrects++; if(dynamic_cast<shape*>(shapes[u])) Nshapes++; } cout << endl <<endl << "circles = " << Ncircles <<endl << "ellipses = " << Nellipses <<endl << "rectangles = " << Nrects <<endl << "shapes = " << Nshapes <<endl << endl << "circle::quantity() = " << circle::quanlity() <<endl << "ellipses::quantity() = " << ellipse::quantity() << endl << "rectangle::quantity() = " << rectangle::quantity() << endl << "shape::quantity() = " << shape::quantity() << endl; shapes.clear(); }
下面总结一下几个知识要点,条理不一定清楚;
(1)、指针是另一种类型;
int和int*是不同的,比如shape* s = new shape,其typeid(s) == typeid(shape*);而typeid(shape) == typeid(*s)。
(2)、引用等同于被引用类型;
这句话说起来非常拗口,但是道理就是这样。本质上引用是阉割版的指针,但是在C++语言层面上还是有很大区别的,它是一个独立的类型,它是和类型实例绑定在一起的。下面这个例子可以同时解释第一条。
class B { public: virtual float f() {return 1.0;} }; class D : public B { }; B* p = new D; B& r = *p;
typeid()看到的指针类型是基类而不是派生类,这证明了第一条结论,此时的p是一个B型的指针类型,typeid()不会去管指针实际指向的目标。typeid()它看到的引用类型则是派生类,*p是使用p进行了一次指针运算操作,然后r就绑定到了一个D型的实例,也就是说引用的关注点在对象上。
typeid(p) == typeid(B*)
typeid(p) != typeid(D*)
typeid(r) == typeid(D)
与此相反,指针指向的类型在typeid()看来是派生类而不是基类,而用一个引用的地址时产生的是基类而不是派生类。这是因为*p依然是一次指针运算操作,它指向了一个对象,而对引用取地址得到的是一个指针类型,相当于对类型实例做了一次取地址运算。
typeid(*p) == typeid(D)
typeid(*p) != typeid(B)
typeid(&r) == typeid(B*)
typeid(&r) != typeid(D*)
(3)、RTTI一般用于多态类型;
多态类型指的是基类中没有虚函数的类型。典型的RTTI是通过在 VTABLE中放一个额外的指针来实现的。这个指针指向一个描述该特定类型的 typeinfo结构(每个新类只产生一个typeinfo的实例),所以typeid()表达式的作用实际上很简单。VPTR用来取typeinfo的指针,然后产生一个结果typeinfo结构的一个引用。dynamic_cast()的过程稍微复杂点,但也需要利用VTABLE中的信息来进行类型转换。
我不知道怎么去访问到VTABLE的typeinfo信息,倒是在找到了一个访问VTABLE中函数信息的例子。这个例子告诉我们VTABLE不止在书上,感兴趣的可以运行一下,或者改改,有可能能找到typeinfo哟!
#include <iostream> using namespace std; class Base { private: int a; public: virtual void fun1() { cout<<"Base::fun1()"<<endl; } virtual void fun2() { cout<<"Base::fun2()"<<endl; } virtual void fun3() { cout<<"Base::fun3()"<<endl; } }; class A:public Base { private: int a; public: void fun1() { cout<<"A::fun1()"<<endl; } void fun2() { cout<<"A::fun2()"<<endl; } }; void foo(Base& obj) { obj.fun1(); obj.fun2(); obj.fun3(); } //int main() //{ // Base b; // A a; // foo(b); // foo(a); //} void *getp(void* p) { return (void*)*(unsigned long *)p; } typedef void (*fun)(); fun getfun(Base* obj, unsigned long off) { void *vptr = getp(obj); unsigned char *p = (unsigned char *)vptr; p += sizeof(void *) * off; cout<<(int)p<<endl; return (fun)getp(p); } int main() { Base *p = new A; fun f = getfun(p,0); (*f)(); f = getfun(p,1); (*f)(); f = getfun(p,2); (*f)(); delete p; }