虚函数及其继承、虚继承类大小

虚函数与继承

空类,空类单继承,空类多继承的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

C++标准规定类的大小不为0,空类的大小为1,当类不包含虚函数和非静态数据成员时,其对象大小也为1。

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

如果在类中声明了虚函数(不管是1个还是多个),那么在实例化对象时,编译器会自动在对象里安插一个指针指向虚函数表(Virtual Table),在32位机器上,一个对象会增加4个字节来存储此指针,它是实现面向对象中多态的关键。而虚函数本身和其他成员函数一样,是不占用对象的空间的。编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。

#include<iostream>
using namespace std;
class Base {

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

};

int main()
{
	Base base;

	cout << "sizeof(Base) = " << sizeof(Base) << " sizeof(base) = " << sizeof(base) << endl;

}

输出

sizeof(Base) = 4 sizeof(base) = 4
其base中成员的存放如下:(即使有其他成员,vfptr也还是放在最前面)



虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符"\0"一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。因为对象bese中多了一个指向虚函数表的指针,而指针的sizeof是4,因此含有虚函数的类或实例最后的sizeof是实际的数据成员的sizeof加4。可以验证一下:

typedef void(*Fun)(void);


int main()
{
	Base base;
	Fun pFun = NULL;

	int** pVtab = (int**)&base;

	pFun = (Fun)pVtab[0][0];
	pFun();

	pFun = (Fun)pVtab[0][1];
	pFun();

	pFun = (Fun)pVtab[0][2];
	pFun();

	cout << pVtab[0][3] << endl;
	

	cout << "sizeof(Base) = " << sizeof(Base) << " sizeof(base) = " << sizeof(base) << endl;

}

输出

Base::f
Base::g
Base::h
0
sizeof(Base) = 4 sizeof(base) = 4

基类含有虚函数的继承

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

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。

可以按上面的方式验证,但是在vs中监视只能看到父类的部分。



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

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()被调用了。这就实现了多态。

int main()
{
	Base base;
	Derived d;
	Fun pFun = NULL;

	int** pVtab = (int**)&d;

	pFun = (Fun)pVtab[0][0];
	pFun();

	pFun = (Fun)pVtab[0][1];
	pFun();

	pFun = (Fun)pVtab[0][2];
	pFun();

	pFun = (Fun)pVtab[0][3];
	pFun();

	pFun = (Fun)pVtab[0][4];
	pFun();

	cout << pVtab[0][5] << endl;
	
	
}

输出

Derived::f1
Base::g
Base::h
Derived::g1
Derived::h1
0
多继承:无虚函数覆盖

假设基类和派生类之间有如下关系:


对于子类实例中的虚函数表,是下面这个样子:

可以看到

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

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


多重继承,含虚函数覆盖

假设,基类和派生类又如下关系:派生类中覆盖了基类的虚函数f


 下面是对于子类实例中的虚函数表的图:


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

虚函数及虚继承

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

当涉及到虚继承时会涉及到虚基类指针,如对Derived3或Derived4定义一个对象d,其里面会出现三个跟虚函数以及虚继承的指针,因为是虚继承,因此引入一个指针指向虚继承的基类,第二由于在基类中有虚函数,因此需要指针指向其虚函数表,由于派生类自己本身也有自己的虚函数,因为采取的是虚继承,因此它自己的虚函数不会放到基类的虚函数表的后面,而是另外分配一个只存放自己的虚函数的虚函数表,于是又引入一个指针,

    上面的这种情况是在VC编译器的情况,但在GCC都是将虚表指针在整个继承关系中共享的,不共享的是指向虚基类的指针。

class A {

    int a;

    virtual ~A(){}

};

class B:virtual public A{

    virtual void myfunB(){}

};

class C:virtual public A{

    virtual void myfunC(){}

};

class D:public B,public C{

    virtual void myfunD(){}

};

在VS情况下:

sizeof(A)=8,sizeof(B)=16,sizeof(C)=16,sizeof(D)=24.

D大小就等于A+B的虚表指针+C的虚表指针+B中的指向虚基类的指针+C中的指向虚基类的指针。

在GCC情况下

sizeof(A)=8,sizeof(B)=12,sizeof(C)=12,sizeof(D)=16.
D大小就等于A+B中的指向虚基类的指针+C中的指向虚基类的指针。


多次虚继承

class A {
public:
	int a;
	
};

class B : virtual public A {
public:
	int b;
	
};

class C : virtual public B {
	
};

结果

sizeof(A) = 4 sizeof(a) = 4
sizeof(B) = 12 sizeof(b) = 12
sizeof(C) = 16 sizeof(c) = 16
不知道是否C与B之间又建立了一个虚基类指针导致C的大小比B大4,虚基类表是否只有一张有待确定。
class A {
public:
    int a;
};
 
class B : virtual public A {
};
 
class C : virtual public A {
};
 
class D : public B, public C{
};
C的大小为12,此时C有两张虚基类表和一个A实例






猜你喜欢

转载自blog.csdn.net/alatebloomer/article/details/80195619
今日推荐