C++之虚函数的原理

1. 看看一个类中如果有虚函数,它的内存布局:

class A{
       double i;
       int j;
       virtual void foo(){}
       virtual void fun(){}
};

内存布局:

1>  class A       size(24):
1>        +---
1>   0   | {vfptr}
1>   8   | i
1>  16  | j
1>        | <alignment member> (size=4)
1>        +---

可以看出,A中有一个vfptr,这个是指向一个virtual table的指针。

C++会给每个有虚函数的类创建一张virtualtable。大小是这个类中虚函数的个数+1/2个slot(用于runtime type identify)。这张virtual table里的每行内容是这个类声明的虚函数的地址。

注意:vfptr在类的位置是固定的,一般放在最前面。它会影响字节对齐的最大字节。

2. 看看如果一个类继承了这个有虚函数的类。它的内存布局:

class Point
{
public:
       virtual ~Point(){};
       virtual float mult(float)=0;
       virtual float y(){return 0;}
       virtual float z(){return 0;}
       float _x;
};
class Point2d:Point
{
public:
       virtual ~Point2d(){};
       virtual float mult(float){return 0;}
       virtual float y(){return 1;}
       float _y;
};
class Point3d:Point2d
{
       virtual ~Point3d(){};
       virtual float mult(float){return 0;}
       virtual float z(){return 0;}
       float _z;
};

注意下图中的vptr放在了类的最后,一般是放在最前面的(VS中),也有放在最后的。这个知道就好了。

可以看出Point的virtual table有4个虚函数。

Point2d的virtualtable有4个虚函数,而看Point2d的类的定义发现,没有修改Point中的z()函数,所以Point2d的virtualtable还保留着Point中的z()函数地址。

每一个子类都有一个虚函数表。

此时注意:我们也可以在子类中定义新的virtual函数(父类中没有的函数),这就会在这个子类的虚函数表中增加一行。

(这里特别注意:什么叫新的virtual函数,如果子类中的virtual函数与父类中的某一virtual函数同名且函数签字完全相同,此时就修改父类中virtual函数地址。否则即使同名,函数签字不相同,也会在虚表中增加新的一行。)

<span style="color:#333333;">class A
{
public: virtual void foo()const{cout<<"A"<<endl;}
};
class B:public A
{
public: virtual voidfoo(){cout<<"B"<<endl;}
};
int _tmain(int argc, _TCHAR* argv[])
{
       A *a=new B;
       a->foo();//A
       return 0;
}</span>

这里B的foo函数签字与A的foo函数签字不同,所以B的虚表中有两行名为foo的函数地址,一个A的,一个是B的。显然主函数中指针a不具有访问B的foo的作用域权限,所以这里不会歧义,就是调用A的foo。

这也是以前所说的多态的3要素:继承,virtual函数,同名函数签字相同才能是多态。

更加注意:这样的原理只适用于非虚继承,虚继承下,就不是这样的原理了。

多态是实质:

Point * p=new Point2d();

p->y();

此时p指向的地址是Point2d的一个对象,显然它的virtual table是Point2d的虚表。

虚函数的调用是通过vptr来调用的。P-y();C++实际的代码是(* p->vptr[3])(p);

p->vptr[3]用来找到y函数地址,而p指向的地址是Point2d的地址,这决定了调用的y函数是Point2d中的y函数。这就实现了多态。

注意此时p虽然指向了Point2d的对象地址,但是此时的p是有作用范围的,它不能调用不属于Point的东西,例如:p->_y;。

3. 多重继承下的虚函数

C继承了A,C继承B,A和B没有关系:

class A{
public:
       char i;
       virtual voidfoo(){cout<<"A"<<endl;}
};
class B
{
public:virtual void foo(int i){cout<<"B"<<endl;}
      virtual void fun(int i){cout<<"B"<<endl;}

};
class C:public A,B
{
};

此时因为A和B中都有一个vfptr,则C中就有两个vfptr了。而两个vfptr在内部是不同名称的。例如:vfptr_A和vfptr_B

内存布局:

1>  class C       size(12):
1>        +---
1>        | +--- (base class A)
1>   0   | | {vfptr}-------------------------------A::foo
1>   4   | | i
1>        | | <alignment member> (size=3)
1>        | +---
1>        | +--- (base class B)
1>   8   | | {vfptr}-------------------------------B::foo、B::fun
1>        | +---
1>        +---

此时如果:A *a=new C(); a->foo();显然是调用A::foo

B* b=new C();b->foo(2);显然是调用B::foo。注意此时b指向的是C中的B的开始位置,不是C的开始位置。

如果Cc;c.foo()/c.foo(2);都是错的。因为C++的寻找机制。首先在C中寻找,没有找到,然后去父类寻找,但是A和B都是父类,且A和B没有先后关系,所以C++同时去寻找,找到了两个foo。注意此时C++寻找机制不关心foo的参数。

如果Cc;c.fun(2);这是对的。因为编译器会遍历两个vfptr,只会找到一个(*c.vfptr_B[2])(&c);它就知道了应该按照这个进行下去。

总结:多重继承下,不要调用多个父类中同名的函数。注意这里即使参数列表不同也不能构成重载函数。其实也很显然,毕竟这些函数属于不同的类。

4. 虚继承下的虚函数

做两个实验:

class A
{
       public:virtual void foo(){cout<<"A"<<endl;}
};
class B:virtual public A
{
       public:virtual void foo(){cout<<"B"<<endl;}
};

内存布局:

1>  class B       size(8):
1>        +---
1>   0   | {vbptr}
1>        +---
1>        +--- (virtual base A)
1>   4   | {vfptr}
1>        +---
class A
{
       public:virtual void foo(){cout<<"A"<<endl;}
};
class B:virtual public A
{
       public:virtual void foo(inti){cout<<"B"<<endl;}
};

内存布局:

1>  class B       size(12):
1>        +---
1>   0   | {vfptr}
1>   4   | {vbptr}
1>        +---
1>        +--- (virtual base A)
1>   8   | {vfptr}
1>        +---

比较可知:虚继承下,如果子类里的虚函数都是重写了虚基类的虚函数,则子类不会单独创建一个vfptr。

如果子类里的虚函数不都是重写了虚基类的虚函数,还有别的虚函数,则子类会单独创建一个vfptr。

注意重写的含义:函数名和函数参数都相同,多态的。

这里还得出一些结论,如果vfptr和vbptr同属于一类时,vfptr在前面,因为vfptr的位置一定是固定的。而vbptr的位置不一样是固定的。但是注意:第一个实验的内存分布,因为vfptr和vbptr不属于同一个类。

注意这些内存布局是在VS下成立的,其他编译器不确定。

时间: 2024-10-06 07:25:05

C++之虚函数的原理的相关文章

虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte

#include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout << "A:A" <<endl; } virtual void getb(){ cout << "A:B" <<endl; } }; class B :public A{ public: B(){} virtual void g

C++中虚函数工作原理和(虚)继承类…

转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7883531 一.虚函数的工作原理 虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数.典型情况下,这一信息具有一种被称为 vptr(virtual table pointer,虚函数表指针)的指针的形式.vptr 指向一个被称为 vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到 vtbl.当

C++中虚函数工作原理

一.虚函数的工作原理 虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数.典型情况下,这一信息具有一种被称为 vptr(virtual table pointer,虚函数表指针)的指针的形式.vptr 指向一个被称为 vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到 vtbl.当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的 vptr 指向的 vtbl,然后在 vtbl 中寻找合适的函数指针. 

虚函数的原理

虚函数是多态机制的一个基础,应该是在运行时刻确定的,因为刚开始程序只知道那是基类的指针类型,但是在运行时刻,可以指向不同的基类,基类当中覆盖的基类当中的方法,会在虚函数表当中覆盖原来的基类当中的函数指针,变为派生类的相应的函数指针. [1] http://www.cnblogs.com/BeyondAnyTime/archive/2012/07/22/2603760.html

C++中虚函数实现原理揭秘

编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟.      编译器对每个包含虚函数的类创建一个表(称为V TA B L E).在V TA B L E中,编译器放置特定类的虚函数地址.在每个带有虚函数的类 中,编译器秘密地置一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E.通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使

C++中虚函数工作原理和(虚)继承类的内存占用大小计算

一.虚继承情况下类的内存大小计算 当每个基类中有多个虚函数时,并且在虚继承的情况下,内存是如何分配的,如何计算类的大小,下面举例说明: #include<iostream> using namespace std; class A { public: int a; virtual void aa(){}; }; class D { public: virtual void dd(){}; }; class C { public: virtual void cc(){}; }; class B

C++虚函数实现原理详解

前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态",这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. 关于虚函数的使用方法,我在这里不做过多的阐述.大家可以看看相关的C++的书籍.在这篇文章中,我只想从虚函数的实现机制上面为大家

[转]虚函数实现原理

前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. 关于虚函数的使用方法,我在这里不做过多的阐述.大家可以看看相关的C++的书籍.在这篇文章中,我只想从虚函数的实现机制上面为大家一个清晰的

虚函数实现原理之虚函数表

引言 C++使用虚函数来实现多态机制,大多数编译器是通过虚函数表来实现动态绑定. 类的内存布局 1.普通类 class B { public: int m; int n; }; int main() { printf("%ld\n", sizeof(B)); B b; printf("%p, %p, %p\n", &b, &b.m, &b.n); return 0; } 类中只有普通成员变量,对象在内存中顺序存储成员变量.输出: 8 0x7f