C++虚函数表和虚函数调用机制、同名非虚函数调用机制

来源:互联网 发布:js合并数组排序 编辑:程序博客网 时间:2024/06/05 20:31
#include <iostream>using namespace std;class Base{public:virtual void f(int a){ cout << "Base::f(int )" << endl; }virtual void g(){ cout << "Base::g()" << endl; }virtual void h(){ cout << "Base::h()" << endl; }void m(){ cout << "Base::m() " << endl; }void m(int a){ cout << "Base::m(int a) " << endl; }};class Derived:public Base{public:void f(){ cout << "Derived::f()" << endl; }void g(){ cout << "Derived::g()" << endl; }void m(){ cout << "Derived::m() " << endl; }void m(int a){ cout << "Derived::m(int a) " << endl; }void n(){ cout << "Derived::n()" << endl; }};class dd:public Derived{public:void f(){ cout << "dd::f()" << endl; }};int main(){//针对虚函数覆盖的具体情况;虚函数调用机制和非虚函数重名函数调用机制实验Base *p=new Derived;p->f(1);p->g();p->h();p->m(1);p->m();p->n();typedef void (*Fun)(void);/*//访问虚函数表Base b;Fun pFun=NULL;cout << "the address of the V-Table is:     "<< (int *)(&b)<< endl;cout << "the first function in the V-Table: "<< (int *)*(int *)(&b) << endl;pFun=(Fun)* ((int *)*(int *)(&b));//dd::f()pFun();pFun=(Fun)* ((int *)*(int *)(&b)+1);//Derived::g()pFun();pFun=(Fun)* ((int *)*(int *)(&b)+2);//Base::h()pFun();*//*//因为Derived从Base继承,所以Derived中也有虚函数,因此Derived类也有自己的V-Table//证明每个含有虚函数的类都会有虚函数表,且被这个类的所有对象共享,如果子类中有//和父类虚函数相同,注意必须完全相同的则会覆盖(重写),而不是重载Derived b;Fun pFun=NULL;cout << "the address of the V-Table is:     "<< (int *)(&b)<< endl;cout << "the first function in the V-Table: "<< (int *)*(int *)(&b) << endl;pFun=(Fun)* ((int *)*(int *)(&b));//Derived::f()pFun();pFun=(Fun)* ((int *)*(int *)(&b)+1);//Derived::g()pFun();pFun=(Fun)* ((int *)*(int *)(&b)+2);//Base::h()pFun();*//*//这里和Derived类的原因相同,并且g在Derived已经把Base的覆盖了dd b;Fun pFun=NULL;cout << "the address of the V-Table is:     "<< (int *)(&b)<< endl;cout << "the first function in the V-Table: "<< (int *)*(int *)(&b) << endl;pFun=(Fun)* ((int *)*(int *)(&b));//dd::f()pFun();pFun=(Fun)* ((int *)*(int *)(&b)+1);//Derived::g()pFun();pFun=(Fun)* ((int *)*(int *)(&b)+2);//Base::h()pFun();*/return 0;}


C++虚函数表和虚函数调用机制、同名非虚函数调用机制

http://baike.baidu.com/view/3750123.htm

http://blog.csdn.net/haoel/article/details/1948051

C++中的虚函数的实现一般是通过虚函数表实现的,尽管C++规范并没有规定具体使用哪种方法来实现虚函数,但大部分的编译器厂商都选择此方法来实现。

类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。

注意:编译器会为每个虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员函数占据函数表中的一个单元,如果类中有N个虚函数,则虚函数表将有N*sizeofx86)字节的大小。

子类中如果有和父类虚函数冲了函数体定义不同其他完全相同的函数则会覆盖父类的虚函数,而不是重载。

另外如果是虚继承如:class B:public virtual A,则在B中还要保存一个vbptr_B_A虚类指针;每个含有虚函数的类都至少含有一个vPtr,如果子类中只是含有父类的虚函数(可以有virtual标识也可以没有),则和父类共享一个vTable即可,即持有一个vPtr;但是如果子类含有父类中没有的虚函数,则子类需要重新创建一个vTable,保存这个vPtr;同时还要持有父类的vPtr。如果并没有含有另外一个虚函数,但是这个时候B中有了构造函数或者虚函数或者都有,则这个时候也要另外持有一份vtable,即要有自己的一份vptr。这个在计算类占有空间大小的时候很有用。参考《程序员面试宝典》P131

 

如果父类虚函数没有被覆盖,则这个虚函数是完全没有意义的。

如有以下两个类的继承情况:

class Base{

public:

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

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

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

    void m(){ cout << "Base::m()" << endl; }

};

 

class Derived:public Base{

public:

    void f(){ cout << "Derived::f()"<< endl; }

    void g(){ cout << "Derived::g()"<< endl; }

    void m(){ cout << "Derived::m()" << endl; }

    void n(){ cout << "Derived::n()"<< endl; }

};

第一种情况:我们使用虚函数一般是

Base*p=newDerived;

p->f(); // Derived::f()

p->g(); // Derived::g()

p->h(); // Base::h()

p->m(); // Base::m()

p->n();// 编译报错

这是因为:f和g都是虚函数,并且在Derived的对象中都已经将这个函数覆盖了(注意要想覆盖必须是除了函数体定义不同其他的完全相同,否则就不能覆盖而视为不同的函数了);h是虚函数但是没有被覆盖;而n只是Derived类对象的成员函数而不是Base类的成员函数。

第一行代码的意义就是:声明一个对象,并把这个对象的指针强制转换成一个Base类型的,我们知道在创建Derived类对象的时候,其中必然包含了Base基类中的所有内容,在进行指针强制转换后,就只会在继承的Base区域内寻找需要执行的对象或者需要操作的数据。根据上面所描述的,f和g被覆盖了,所以调用的时候就会调用Derived的,h没有被覆盖所以调用Base的h,而n根本就不在这个区域内所以会编译报错:找不到这个成员。

 

第二种情况:

Derived*p=newDerived;

p->f(); // Derived::f()

p->g(); // Derived::g()

p->h(); // Base::h()

p->m(); // Derived::m()

p->n();// Derived::n()

这个时候并没有把Derived类对象的指针做强制转换,这个时候就会在Derived的区域内寻找数据和函数进行执行;这个时候实际上在Derived对象的体内,因为f和g被覆盖了,所以只含有一个f和g而且是Derived的f和g;h没有被覆盖但是h是Base的h;总之对于虚函数的调用是通过查虚函数表来实现的。m在Base和Derived中都有定义但是不是虚函数,所以没有覆盖在这里也不是重载,而是属于两个不同类的函数,在具体调用的时候首先是根据函数名称即标识符来查看调用哪个类中的m函数,这个查找过程是是又一定的名字查找规则的,尤其是在多重继承中体现更明显,在C++编程思想第二卷P374中有具体查找规则描述;确定类了之后再选择函数具体的重载形式,比如如果在Base中m还有一个参数为int的重载形式,而在Derived中没有,但是在调用的时候使用p->m(1)调用,在这个例子的情形下就会报错,因为根据名称查找规则确定是使用Derived类中的m函数,但是这个类中没有参数为int的重载形式,如果这在Derived中重载了这种形式则肯定没有问题;同样在Derived类中如果不定义m则也没有问题,因为这个时候在名称查找的时候就会确定的是使用Base类中的m函数,而在Base类中恰好有m的int参数的重载形式,所以就不会报错,只是这个时候就会调用Base类中的m函数了。

综上两种情况描述了目前我所见到的类继承中的两大类情形:一种就是虚函数的调用机制;另一种就是同名函数的查找和调用机制。