虚函数与继承

    1. 空类,空类单继承,空类多继承的sizeof

    #include <iostream>
    using namespace std;
    
    class Base1
    {
    
    };
    
    class Base2
    {
    
    };
    
    class Derived1:public Base1
    {
    
    };
    
    class Derived2:public Base1, public Base2
    {
    
    };
    
    int main() 
    { 
        Base1 b1;
        Base2 b2;
        Derived1 d1;
        Derived2 d2;
        cout<<"sizeof(Base1) = "<<sizeof(Base1)<<" sizeof(b1) = "<<sizeof(b1)<<endl;
         cout<<"sizeof(Base2) = "<<sizeof(Base2)<<" sizeof(b2) = "<<sizeof(b2)<<endl;
        cout<<"sizeof(Derived1) = "<<sizeof(Derived1)<<" sizeof(d1) = "<<sizeof(d1)<<endl;
        cout<<"sizeof(Derived2) = "<<sizeof(Derived2)<<" sizeof(d1) = "<<sizeof(d1)<<endl;
    
        return 0; 
    }

    结果为:
    sizeof(Base1) = 1 sizeof(b1) = 1
    sizeof(Base2) = 1 sizeof(b2) = 1
    sizeof(Derived1) = 1 sizeof(d1) = 1
    sizeof(Derived2) = 1 sizeof(d1) = 1
    可以看出所有的结果都是1。

    为什么一个空的类X会有占据1byte的内存空间呢?《Inside The C++ Object Model》给出了解释。
    实际上一个空的类并不是空的,它有一个隐晦的1byte,那是被编译器安插进去的一个char。这使得这个class的两个objects得以在内存中配置独一无二的地址,对两个不同的object加以区分。


    2.含有虚函数的类以及虚继承类的sizeof

    虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。

    假设我们有这样的一个类:

    class Base {
    public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
    };

    当我们定义一个这个类的实例,Base b时,其b中成员的存放如下:
    转载
    指向虚函数表的指针在对象b的最前面。
    虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。
    因为对象b中多了一个指向虚函数表的指针,而指针的sizeof是4,因此含有虚函数的类或实例最后的sizeof是实际的数据成员的sizeof加4。
    下面将讨论针对基类含有虚函数的继承讨论。

    (1)在派生类中不对基类的虚函数进行覆盖,同时派生类中还拥有自己的虚函数,比如有如下的派生类:

    class Derived: public Base
     {
    public:
    virtual void f1() { cout << "Derived::f1" << endl; }
    virtual void g1() { cout << "Derived::g1" << endl; }
    virtual void h1() { cout << "Derived::h1" << endl; }
    };

    基类和派生类的关系如下:
    这里写图片描述

    当定义一个Derived的对象d后,其成员的存放如下:
    这里写图片描述
    可以发现:
    1)虚函数按照其声明顺序放于表中。
    2)父类的虚函数在子类的虚函数前面。
    此时基类和派生类的sizeof都是数据成员的sizeof加4。

    (2)在派生类中对基类的虚函数进行覆盖,假设有如下的派生类:

    class Derived: public Base
     {
    public:
    virtual void f() { cout << "Derived::f" << endl; }
    virtual void g1() { cout << "Derived::g1" << endl; }
    virtual void h1() { cout << "Derived::h1" << endl; }
    };

    基类和派生类之间的关系:其中基类的虚函数f在派生类中被覆盖了:
    这里写图片描述
    当我们定义一个派生类对象d后,其d的成员存放为:
    这里写图片描述
    可以发现:
    1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
    2)没有被覆盖的函数依旧。

    这样,我们就可以看到对于下面这样的程序,
    Base *b = new Derive();
    b->f();
    由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

    (3)多继承:无虚函数覆盖

    假设基类和派生类之间有如下关系:
    这里写图片描述
    对于子类实例中的虚函数表,是下面这个样子:
    这里写图片描述
    我们可以看到:
    1) 每个父类都有自己的虚表。
    2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)。

    由于每个基类都需要一个指针来指向其虚函数表,因此d的sizeof等于d的数据成员加3*4=12。

    (4)多重继承,含虚函数覆盖

    假设,基类和派生类又如下关系:派生类中覆盖了基类的虚函数f
    这里写图片描述

    下面是对于子类实例中的虚函数表的图:
    这里写图片描述

    我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

    Derive d;
    Base1 *b1 = &d;
    Base2 *b2 = &d;
    Base3 *b3 = &d;
    b1->f(); //Derive::f()
    b2->f(); //Derive::f()
    b3->f(); //Derive::f()
    b1->g(); //Base1::g()
    b2->g(); //Base2::g()
    b3->g(); //Base3::g()

    3.一个关于含虚函数及虚继承的sizeof计算

    #include <iostream>
    using namespace std;
    
    class Base
    {
    public:
        virtual void f();
        virtual void g();
        virtual void h();
    };
    
    class Derived1: public Base
    {
    public:
        virtual void f1();
        virtual void g1();
        virtual void h1();
    };
    
    class Derived2:public Base
    {
    public:
        virtual void f();
        virtual void g1();
        virtual void h1();
    };
    
    class Derived3:virtual public Base
    {
    public:
        virtual void f1();
        virtual void g1();
        virtual void h1();
    };
    
    class Derived4:virtual public Base
    {
    public:
        virtual void f();
        virtual void g1();
        virtual void h1();
    };
    
    class Derived5:virtual public Base
    {
    public:
        virtual void f();
        virtual void g();
        virtual void h();
    };
    
    class Derived6:virtual public Base
    {
    
    };
    
    int main() 
    { 
        cout<<sizeof(Base)<<endl; //4
        cout<<sizeof(Derived1)<<endl; //4
        cout<<sizeof(Derived2)<<endl; //4
        cout<<sizeof(Derived3)<<endl; //12
        cout<<sizeof(Derived4)<<endl; //12
        cout<<sizeof(Derived5)<<endl; //8
        cout<<sizeof(Derived6)<<endl; //8
    
        return 0; 
    }

    对于Base, Derived1和Derived2的结果根据前面关于继承的分析是比较好理解的,不过对于虚继承的方式则有点不一样了,根据结果自己得出的一种关于虚继承的分析,如对Derived3或Derived4定义一个对象d,其里面会出现三个跟虚函数以及虚继承的指针,因为是虚继承,因此引入一个指针指向虚继承的基类,第二由于在基类中有虚函数,因此需要指针指向其虚函数表,由于派生类自己本身也有自己的虚函数,因为采取的是虚继承,因此它自己的虚函数不会放到基类的虚函数表的后面,而是另外分配一个只存放自己的虚函数的虚函数表,于是又引入一个指针,从例子中看到Derived5和Derived6的结果是8,原因是在派生类要么没有自己的虚函数,要么全部都是对基类虚函数的覆盖,因此就少了指向其派生类自己的虚函数表的指针,故结果要少4。(这个是个人的分析,但原理不知道是不是这样的).

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

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

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

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值