C++RTTI小总结

= 601

        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不止在书上,感兴趣的可以尝试一下。

#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;  
}  

发表评论