c++对象模型和RTTI(runtime type information)

在前面已经探讨过了虚继承对类的大小的影响,这次来加上虚函数和虚继承对类的大小的影响。

先来回顾一下之前例子的代码:

#include <iostream>
using namespace std;

class BB {
public:
    int bb_;
};

class B1 : virtual public BB {
public:
    int b1_;
};

class B2 : virtual public BB {
public:
    int b2_;
};

class DD : public B1, public B2 {
public:
    int dd_;
};

int main(void) {
    cout<<sizeof(BB)<<endl;
    cout<<sizeof(B1)<<endl;
    cout<<sizeof(DD)<<endl;

    B1 b1;
    long** p;
    cout<<&b1<<endl;//类的首地址
    cout<<&b1.bb_<<endl;
    cout<<&b1.b1_<<endl;

    p = (long**)&b1;//这表示是vbptr
    cout<<p[0][0]<<endl;//取出vbptr指向的vbtl的第一个数据项
    cout<<p[0][1]<<endl;//取出vbptr指向的vbtl的第二个数据项

    DD dd;
    cout<<&dd<<endl;//类的首地址
    cout<<&dd.bb_<<endl;
    cout<<&dd.b1_<<endl;
    cout<<&dd.b2_<<endl;
    cout<<&dd.dd_<<endl;
    p = (long**)&dd;
    cout<<p[0][0]<<endl;
    cout<<p[0][1]<<endl;
    cout<<p[2][0]<<endl;
    cout<<p[2][1]<<endl;

    dd.bb_ = 10;

    BB* pp;
    pp = &dd;
    pp->bb_;//这是通过间接访问,需要运行时的支持

    return 0;
}

编译运行:

而数据模型为:

关于对虚继承的详细分析可以参考博文:http://www.cnblogs.com/webor2006/p/5621825.html

下面在这个例子上加上虚函数来进一步讨论数据模型:

#include <iostream>
using namespace std;

class BB {
public:
    virtual void vfbb() {
        cout<<"BB::vfbb()"<<endl;
    }

    virtual void vfbb2() {
        cout<<"BB::vfbb2()"<<endl;
    }

    int bb_;
};

class B1 : virtual public BB {
public:
    virtual void vfb1() {
        cout<<"B1::vfb1()"<<endl;
    }

    int b1_;
};

class B2 : virtual public BB {
public:
    virtual void vfb2() {
        cout<<"B2::vfb2()"<<endl;
    }
    int b2_;
};

class DD : public B1, public B2 {
public:
    virtual void vfdd() {
        cout<<"DD::vfdd()"<<endl;
    }

    int dd_;
};

int main(void) {
    cout<<sizeof(BB)<<endl;
    cout<<sizeof(B1)<<endl;
    cout<<sizeof(DD)<<endl;

    //B1 b1;
    //long** p;
    //cout<<&b1<<endl;//类的首地址
    //cout<<&b1.bb_<<endl;
    //cout<<&b1.b1_<<endl;

    //p = (long**)&b1;//这表示是vbptr
    //cout<<p[0][0]<<endl;//取出vbptr指向的vbtl的第一个数据项
    //cout<<p[0][1]<<endl;//取出vbptr指向的vbtl的第二个数据项

    //DD dd;
    //cout<<&dd<<endl;//类的首地址
    //cout<<&dd.bb_<<endl;
    //cout<<&dd.b1_<<endl;
    //cout<<&dd.b2_<<endl;
    //cout<<&dd.dd_<<endl;
    //p = (long**)&dd;
    //cout<<p[0][0]<<endl;
    //cout<<p[0][1]<<endl;
    //cout<<p[2][0]<<endl;
    //cout<<p[2][1]<<endl;

    return 0;
}

编译运行:

下面还是先画一下它的内存模型,然后再用代码来验证:

先来看下BB类:

编译运行:

下面来分析一下B1类:

下面来验证:

typedef void (*FUNC)();

int main(void) {
    cout<<sizeof(BB)<<endl;
    cout<<sizeof(B1)<<endl;
    cout<<sizeof(DD)<<endl;


    BB bb;
    long** p = (long**)&bb;
    FUNC fun;
    fun = (FUNC)p[0][0];
    fun();
    fun = (FUNC)p[0][1];
    fun();
    
    cout<<"----------------------------"<<endl;

    B1 b1;
    p = (long**)&b1;
    fun = (FUNC)p[0][0];
    fun();
    cout<<p[1][0]<<endl;
    cout<<p[1][1]<<endl;
    fun = (FUNC)p[3][0];
    fun();
    fun = (FUNC)p[3][1];
    fun();

    //DD dd;
    //cout<<&dd<<endl;//类的首地址
    //cout<<&dd.bb_<<endl;
    //cout<<&dd.b1_<<endl;
    //cout<<&dd.b2_<<endl;
    //cout<<&dd.dd_<<endl;
    //p = (long**)&dd;
    //cout<<p[0][0]<<endl;
    //cout<<p[0][1]<<endl;
    //cout<<p[2][0]<<endl;
    //cout<<p[2][1]<<endl;

    return 0;
}

编译运行:

下面再来分析一下DD类:

同样用代码来验证:

typedef void (*FUNC)();

int main(void) {
    cout<<sizeof(BB)<<endl;
    cout<<sizeof(B1)<<endl;
    cout<<sizeof(DD)<<endl;


    BB bb;
    long** p = (long**)&bb;
    FUNC fun;
    fun = (FUNC)p[0][0];
    fun();
    fun = (FUNC)p[0][1];
    fun();
    
    cout<<"----------------------------"<<endl;

    B1 b1;
    p = (long**)&b1;
    fun = (FUNC)p[0][0];
    fun();
    cout<<p[1][0]<<endl;
    cout<<p[1][1]<<endl;
    fun = (FUNC)p[3][0];
    fun();
    fun = (FUNC)p[3][1];
    fun();

    cout<<"----------------------------"<<endl;

    DD dd;
    p = (long**)&dd;
    fun = (FUNC)p[0][0];
    fun();
    cout<<p[1][0]<<endl;
    cout<<p[1][1]<<endl;
    fun = (FUNC)p[3][0];
    fun();
    cout<<p[4][0]<<endl;
    cout<<p[4][1]<<endl;
    fun = (FUNC)p[7][0];
    fun();
    fun = (FUNC)p[7][1];
    fun();

    return 0;
}

编译运行:

有了虚继承和虚函数的类的内存模型是比较复杂的,需细细体会。下面来讨论一个新的东东:

 对于C++的数据模型,实际上它还包括另外一些信息,也就是RTTI,以便在运行时进行类型识别,C++的运行时类型识别主要是由dynamic_cast运算符、typeid运算符、type_info来支持下,下面具体来学习下:

#include <iostream>
using namespace std;

class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() {

    }
};

class Circle : public Shape {//圆形
public:
    void draw() {
        cout<<"Circle::draw ..."<<endl;
    }
};

class Square : public Shape {//正方形
public:
    void draw() {
        cout<<"Square::draw ..."<<endl;
    }
};


int main(void) {
    Shape* p;
    Circle c;

    p = &c;
    p->draw();

    return 0;
}

编译运行:

以上输出毫无疑问,这时可以用dynamic_cast运算符来进行类型识别:

#include <iostream>
using namespace std;

class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() {

    }
};

class Circle : public Shape {//圆形
public:
    void draw() {
        cout<<"Circle::draw ..."<<endl;
    }
};

class Square : public Shape {//正方形
public:
    void draw() {
        cout<<"Square::draw ..."<<endl;
    }
};


int main(void) {
    Shape* p;
    Circle c;

    p = &c;
    p->draw();

    if(dynamic_cast<Circle*>(p)) {
        cout<<"p is point to a Circle Object"<<endl;
    } else if(dynamic_cast<Square*>(p)) {
        cout<<"p is point to a Square Object"<<endl;
    } else {
        cout<<"p is point to a Other Object"<<endl;
    }

    return 0;
}

编译运行:

这时就可以做安全的向下转型:

这里要提醒一下:在VS C++中要想支持这个类型识别,需要进行一个设置才行,否则是不支持的:

如果选择“否(/GR-)”,再次运行则会给出警告了:

如果运行的话会直接报错的:

所以还是需要将其打开,将配置还原才行。

现在已经学到几个转换相关的函数了,下面来总结一下:

static_cast:用在编译器认可的转型。

reinterpret_cast:用在编译器不认可的转型。

const_cast:去除常量性。

以上三个都是静态转型,不需要运行时支持。

而这里用的的dynamic_cast是安全向下转型,是动态转型,需要运行时的支持。

对于这个运算符它返回的是type_info对象,它的结构如下:

class type_info {
public:
    virtual ~type_info();
    bool operator==(const type_info& rhs) const;
    bool operator!=(const type_info& rhs) const;
    int before(const type_info& rhs) const;
    const char* name() 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);
};

下面用它来打印一下:

编译运行:

所以就可以这样来写判断语句:

int main(void) {
    Shape* p;
    Circle c;

    p = &c;
    p->draw();

    if(dynamic_cast<Circle*>(p)) {
        cout<<"p is point to a Circle Object"<<endl;
        Circle* cp = dynamic_cast<Circle*>(p);  //安全向下转型
        cp->draw();
    } else if(dynamic_cast<Square*>(p)) {
        cout<<"p is point to a Square Object"<<endl;
    } else {
        cout<<"p is point to a Other Object"<<endl;
    }

    //typeid运算符
    cout<<typeid(*p).name()<<endl;
    cout<<typeid(Circle).name()<<endl;
    if(typeid(*p).name() == typeid(Circle).name()) {
        cout<<"p is point to a Circle Object"<<endl;
        ((Circle*)p)->draw();
    }else if(typeid(*p).name() == typeid(Square).name()) {
        cout<<"p is point to a Square Object"<<endl;
        ((Square*)p)->draw();
    } else {
        cout<<"p is point to a Other Object"<<endl;
    }

    return 0;
}

编译运行:

【注意】

①、由于type_info的构造函数和=号运算符是私有的:

所以不能这样写:

所以代码中是“typeid(Circle).name()”直接来用。

②、这样的类型转换都没有通过多态来访问效率来得高:

③、reinterpret_cast和C风格的强制转让换换还是有区别的:

原文地址:https://www.cnblogs.com/webor2006/p/5689716.html