明确以下几点:

  1. 多态的实现是依靠虚函数表,程序需要额外的查询虚函数表的开销。

  2. C++的构造函数中可以调用虚函数,说明虚函数表的产生是在构造函数调用之前。虚函数表的建立是在编译时(Compile-Time)。

    看一个例子:

    #include <iostream>
    using namespace std;
    class A
    {
        int m1;
        int m2;
    public:
        A()
        {
            fun2();
        }
        virtual void fun2()
        {
            cout<<"call A::fun2 success!"<<endl;
        }
    };
    class B:public A
    {
    public:
        B()
        {
            fun2();
        }
        void fun2()
        {
            cout<<"call B::fun2 success!"<<endl;
        }
    };
    int main()
    {
        B *pa = new B();
        return 1;
    }

    输出结果:

    call A::fun2 success!

    call B::fun2 success!

    说明两个构造函数内的虚函数都调用成功。由于对象的构造顺序和子类对基类虚函数的重载还未完成,所以第一个fun函数是调用的A的虚函数。

    (在JAVA中会输出两个call B::fun2 success!,WHY??)

  3. 虚函数表的地址vptr是存在对象的this指针指向的结构体的第一个数据位置,即存储vptr的地址和this的地址值相同。

  4. 一个类的所有对象都共享同一份虚函数表。

看一个例子:

#include <iostream>
using namespace std;
class A
{
    int m1;
    int m2;
public:
    virtual void fun2()
    {
        cout<<"call A::fun2 success!"<<endl;
    }
    void fun()
    {
        cout<<this<<' '<<&m1<<' '<<hex<<*((int*)this)<<endl;
        //*((int*)this)取vptr的值
    }
};
int main()
{
    A *pa1 = new A();
    A *pa2 = new A();
    pa1->fun();
    pa2->fun();
    return 1;
}

输出结果:

00332BA0 00332BA4 46F01C

00332BD8 00332BDC 46F01C

两个对象的this值不同,数据成员的地址也不同,但是其中的vptr值是相同的。


5.  虽然子类重载了基类的虚函数,但是通过子类对象依然可以访问到基类的虚函数。

看例子:

#include <iostream>
using namespace std;
class A
{
    int m1;
    int m2;
public:
    virtual void fun2()
    {
        cout<<"call A::fun2 success!"<<endl;
    }
};
class B : public A
{
public:
    virtual void fun2()
    {
        cout<<"call B::fun2 success!"<<endl;
    }
};
int main()
{
    A *pa = new B();
    pa->A::fun2();
    return 1;
}

输出结果:

call A::fun2() success!

也就是说,在B的虚函数表中,依然存在着A的虚函数的入口地址,而且是可以调用的。