zoukankan      html  css  js  c++  java
  • 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风格的强制转让换换还是有区别的:

  • 相关阅读:
    k8s 存活探针(健康检查)
    数据库CPU 100%处理记录
    zabbix 批量安装+自动注册
    Docker 学习目录
    ubuntu18启动zabbix-agent失败/故障记录
    使用Docker构建企业Jenkins CI平台
    记一次服务被黑处理过程
    ELK数据迁移,ES快照备份迁移
    脚本监控服务状态 微信-钉钉告警
    邮箱附件脚本
  • 原文地址:https://www.cnblogs.com/webor2006/p/5689716.html
Copyright © 2011-2022 走看看