探索c++对象模型

一、探索虚函数表

虚函数表是通过一块连续内存存储虚函数的地址

这张表解决了继承、虚函数(重写)的问题。

含有虚函数的类的对象实例中都存在一张虚函数表,编译器都会维护这张虚表,对象的前四个字节就是指向虚表的指针,虚函数表就像一张地图,指明了实际应该调用的虚函数。

虚表是所有类对象实例共用的

(1)在不含虚函数的类的对象实例中一定不含虚函数表指针

#include<iostream>
using namespace std;
class A
{
public:
    A()
        :_a(1)
    {}
    void show()
    {
        cout<<"A::show()"<<endl;
    }
private:
    int _a;
};
int main()
{
    A a;
    cout<<sizeof(a)<<endl;
    typedef void (*v_ftr)();//定义一个函数指针类型;该类型的函数指针指向返回值为void,参数为空的函数
    (*(v_ftr*)(*(int*)(&a)))();
    (*(v_ftr*)(*(int*)(&a)+4))();
    return 0;
}

这里写图片描述

(2)在含有虚函数的类的对象实例中都存在指向虚函数表的指针,且该指针所指的虚函数表(即函数指针数组)中只存该类的所有虚函数的地址;且该指针数组最后一个元素为NULL指针;

//1. 探索虚表
#include<iostream>
using namespace std;
class A
{
public:
    A()
        :_a(1)
    {}
    virtual void show()
    {
        cout<<"A::show()"<<endl;
    }
    virtual void show1()
    {
        cout<<"A::show1()"<<endl;
    }
    void show2()
    {
        cout<<"A::show3()"<<endl;
    }
private:
    int _a;
};


int main()
{
    A a;
    typedef void (*v_ftr)();//定义一个函数指针类型;该类型的函数指针指向返回值为void,参数为空的函数
   (*(v_ftr*)(*(int*)(&a)))();
   (*(v_ftr*)(*(int*)(&a)+4))();
    return 0;
}

由于监视器有时候会欺骗我们,所以分别从监视器和内存两个角度去看
这里写图片描述

这里写图片描述

这里写图片描述

二、探索单继承对象模型

(1)在继承关系中,如果父类中含有虚函数,且子类中也含有虚函数;那么子类

对象中没有专门的虚函数指针,来指向一块专门开辟好用来存放子类特有的(不是

重写父类虚函数)虚函数的虚函数表;而是,子类和父类共有父类的虚函数表;但

是在共用期间,如果子类中有对父类虚函数重写的虚函数;那么这时会修改该虚函

数表的内容,即就是在原先存放父类虚函数地址的位置上 写上子类重写的虚函数

的地址;这样就将父类的重写虚函数覆盖;这就是多态的实现机制;

那么在用父类的 指针或者引用 指向或者引用 父类的对象时,这时虚函数表中存放

的只是父类的虚函数,所以调用同名的重写虚函数,会调到父类的成员函数;但是

用父类的 指针或者引用 指向或者引用 子类的对象时;这时虚函数表中原先存放父

类重写虚函数地址的位置已经被改为子类重写虚函数的地址,所以调用同名的重写

虚函数,会调到子类的成员函数;

这就是动态多态实现的原理;

#include<iostream>
using namespace std;
class A
{
public:
    A()
        :_a(1)
    {}
    virtual void show()
    {
        cout<<"A::show()"<<endl;
    }
    virtual void show1()
    {
        cout<<"A::show1()"<<endl;
    }
private:
    int _a;
};
class B:public A
{
public:
    B()
        :_b(2)
    {}
    virtual void show()
    {
        cout<<"B::show"<<endl;
    }
    virtual void show2()
    {
        cout<<"B::show2()"<<endl;
    }
    virtual void show3()
    {
        cout<<"B::show3()"<<endl;
    }
private:
    int _b;
};
typedef void (*v_ptr)();//定义一个函数指针类型;该类型的函数指针指向返回值为void,参数为空的函数
void Printf(v_ptr* _pptr)
{
    int i=0;
    for(i=0;_pptr[i]!=NULL;i++)
    {
        _pptr[i]();
    }
}
int main()
{
    B b;
    Printf((v_ptr*)(*(int*)(&b)));
    return 0;
}

这里写图片描述

当子类中重写了父类的虚函数时,通过监视器看到的对象模型不全(这是vs2008的bug)

这里写图片描述

说明:派生类虚函数表生成顺序

1.虚函数按照其声明顺序存在于虚表中;

2.在派生类对象中;虚函数表的前面是父类的虚函数,后面是子类的虚函数;(有覆盖的情况下,子类的覆盖的虚函数会存在于父类覆盖的虚函数相应的位置上)

缺点:

会有效率低的问题;
安全问题;

总结:

(1)一个类只要有一个虚函数,它就会被编译器分配一个虚表指针,也就是__vfptr,用来存储虚函数的地址;

(2)子类的虚函数表是在父类的虚函数表上进行修改的,就像上边的对象模型所示,B类的虚函数就是在A类的虚函数之后;

(3)父类中的虚函数被子类改写;也就是说,当子类中含有与父类的虚函数 函数名相同,参数列表相同,返回值相同的函数(协变除外),这样就构成了重写,父类中的虚函数地址被子类改写;

  • 下边再次区分几个容易混淆的概念–重载重写(覆盖)重定义(隐藏)

    重载–在同一个作用域内,函数名相同,参数列表不同,返回值可以相同可以不同的两个函数可以构成重载,需要声明的是,c++语言中支持函数的重载,而c语言中不支持函数的重载。原因是c语言和c++对函数的处理是不同的。

    重写(覆盖)–在不同的作用域中(分别在父类和子类中),函数名相同,参数列表,返回值都相同(协变除外),并且父类的函数是虚函数,访问限定符可同可不同的两个函数就构成了重写。

     协变:协变也是一种重写,只是父类和子类中的函数的返回值不同,父类的函数返回父类的指针或者引用,子类函数返回子类的指针或者引用。

重定义(隐藏)–在不同的作用域中(分别在父类和子类),函数名相同,只要不是构成重写就是重定义。

(4)只有类的成员函数可以定义为虚函数,静态成员函数不能被定义为虚函数;

三、探索多继承对象模型

这里写图片描述

【多继承关系中子类重写父类虚函数】

#include<iostream>
using namespace std;
class Base1
{
public:
    Base1()
        :ibase1(1)
    {}
    virtual void f()
    {
        cout<<"Base1::f()"<<endl;
    }
    virtual void g()
    {
        cout<<"Base1::g()"<<endl;
    }
    virtual void h()
    {
        cout<<"Base1::h()"<<endl;
    }
private:
    int ibase1;
};
class Base2
{
public:
    Base2()
        :ibase2(2)
    {}
    virtual void f()
    {
        cout<<"Base2::f()"<<endl;
    }
    virtual void g()
    {
        cout<<"Base2::g()"<<endl;
    }
    virtual void h()
    {
        cout<<"Base2::h()"<<endl;
    }
private:
    int ibase2;
};
class Base3
{
public:
    Base3()
        :ibase3(3)
    {}
    virtual void f()
    {
        cout<<"Base3::f()"<<endl;
    }
    virtual void g()
    {
        cout<<"Base3::g()"<<endl;
    }
    virtual void h()
    {
        cout<<"Base3::h()"<<endl;
    }
private:
    int ibase3;
};
class Derive:public Base1,public Base2,public Base3
{
public:
    Derive()
        :iderive(4)
    {}
    virtual void f()//重写
    {
        cout<<"Derive::f()"<<endl;
    }
    virtual void g1()
    {
        cout<<"Derive::g1()"<<endl;
    }
private:
    int iderive;
};
typedef void (*ptr)();//定义一个函数指针类型;
void Printf(ptr* pptr)
{
    int i=0;
    for(i=0;pptr[i]!=NULL;i++)
    {
        pptr[i]();//调用函数指针所指的函数
    }
}
int main()
{
    Derive d;
    Printf((ptr*)(*(int*)&d));
    cout<<endl;
    Printf((ptr*)(*((int*)(&d)+2)));
    cout<<endl;
    Printf((ptr*)(*((int*)(&d)+4)));
    return 0;
}

这里写图片描述

【多继承关系中子类没有重写父类虚函数】

代码和上面的一样;将Derive类中的f()函数去掉;不在对父类的虚函数进行重写;
这里写图片描述

多继承关系对象模型的总结:

  • 多继承关系中,如果父类中含有虚函数,且子类当中没有对父类的虚函数进行重写,子类中也定义了自己的虚函数;那么这时,子类会将父类的成员继承下来;并且将各个父类的虚表指针也继承下来;此时,子类将自己类中新定义的虚函数的地址放在继承列表中第一个父类的虚函数指针所指的虚函数列表的后面;仅此而已;子类不会为自己新开辟虚函数指针指向一块新开辟的虚函数表内存,存放自己的虚函数地址;

  • 如果父类中含有虚函数,且子类中对父类的虚函数进行了写,那么在继承下来的父类的虚函数表中,以前存放父类该虚函数地址的位置,会被重写为子类的重写虚函数的地址;然后,子类将自己类中新定义的普通虚函数的地址放在继承列表中第一个父类的虚函数指针所指的虚函数列表的后面;

在多重继承体系下,有n个含有虚函数的父类,派生类中就有n个虚函数表,最终子类的虚函数是在第一个父类的虚函数表中;

新发现:

在vs2008中,查看子类对象模型,打开监视器,不会显示继承列表中第一个父类的虚函数指针所指的虚函数列表后添加的子类的虚函数的地址和子类虚函数的原型;(这时vs2008的bug)

四、菱形继承的对象模型(含有虚函数覆盖)

这里写图片描述

#include<iostream>
using namespace std;

class B
{
public:
    B()
        :ib(1)
        ,cb('1')
    {}
    virtual void f()
    {
        cout<<"B::f()"<<endl;
    }
    virtual void Bf()
    {
        cout<<"B::Bf()"<<endl;
    }
public:
    int ib;
    char cb;
};
class B1:public B
{
public:
    B1()
        :ib1(2)
        ,cb1('2')
    {}
    virtual void f()//重写父类的虚函数f()
    {
        cout<<"B1::f()"<<endl;
    }
    virtual void f1()
    {
        cout<<"B1::f1()"<<endl;
    }
    virtual void Bf1()
    {
        cout<<"B1::Bf1()"<<endl;
    }
public:
    int ib1;
    char cb1;
};
class B2:public B
{
public:
    B2()
        :ib2(3)
        ,cb2('3')
    {}
    virtual void f()//重写父类的虚函数f()
    {
        cout<<"B2::f()"<<endl;
    }
    virtual void f2()
    {
        cout<<"B2::f2()"<<endl;
    }
    virtual void Bf2()
    {
        cout<<"B2::Bf2()"<<endl;
    }
public:
    int ib2;
    char cb2;
};
class D:public B1,public B2
{
public:
    D()
        :id(4)
        ,cd('4')
    {}
    virtual void f()//重写父类的虚函数f()
    {
        cout<<"D::f()"<<endl;
    }
    virtual void f1()//重写父类的虚函数f1()
    {
        cout<<"D::f1()"<<endl;
    }
    virtual void f2()//重写父类的虚函数f2()
    {
        cout<<"D::f2()"<<endl;
    }
    virtual void Df()//特有的
    {
        cout<<"D::Df()"<<endl;
    }
public:
    int id;
    char cd;
};

typedef void(*ptr)();//定义一个函数指针类型

void Printf(ptr* pptr)
{
    int i=0;
    for(i=0;pptr[i]!=NULL;i++)
    {
        pptr[i]();
    }
}
int main()
{
    D d;
    Printf((ptr*)(*(int*)&d));
    cout<<endl;
    Printf((ptr*)(*((int*)&d+5)));
    return 0;
}

这里写图片描述

发现一个问题:B2中的父类虚函数表的没有以NULL作为结尾;(待搞懂)

五、菱形虚拟继承的对象模型(含有虚函数覆盖)

还是上面的代码;将由B类继承生成B1类,和由B类继承生成B2类时的继承方式改为虚拟继承即可;

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值