虚函数的工作原理

  • 为什么需要虚函数?
    在编写程序的过程中,一定会遇到这样的场景。派生类继承了基类的方法,但是我们希望同一个方法在派生类和基类中的行为不同,就是方法取决于调用的对象(这种复杂的行为就是多态)。实现多态的公有继承有两种重要的机制。
  1. 在派生类中重新定义基类方法 (可以定义不同方法)
  2. 虚函数 (可以使基类引用或指针可以调用派生类新定义的方法)
  • 虚函数基本语法?
    在需要重新定义的方法前添加关键字 virtual,然后分别定义即可。
  • 编译器如何确定作者想使用哪种方法?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class person{
    virtual void show(); //具体定义就不写了,反正和派生类不一样
    };
    class student : public person{
    virtual void show();
    };
    person temp_person(……); //构造函数,实例化一个对象
    student temp_student(……);
    temp_person.show(); //person::show()
    temp_student.show(); //student::show()
    person & temp_s = temp_student;
    person & temp_p = temp_person;
    temp_s.show(); //person::show()
    temp_p.show(); //student::show()

如上述代码所示,基类和派生类有不一样的方法,那么我在使用的时候,我怎么判断调用的是哪个方法。
如果使用virtual,根据引用或指针指向的对象类型来选择方法。
如果不是使用virtual,根据引用或指针的类型来选择方法。(这个没有太懂,来自C++ primer plus p493,如果重名了,参数列表还相同……感觉死机了额)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class person{
void show();
};
class student : public person{
void show(); //这里涉及隐藏规则,具体在 effective C++ third edition p156
};
person temp_person(……); //构造函数,实例化一个对象
student temp_student(……);
temp_person.show(); //person::show()
temp_student.show(); //student::show()
person & temp_s = temp_student;
person & temp_p = temp_person;
temp_s.show(); //person::show()
temp_p.show(); //person::show() 这里不确定呀

  • 实现原理
    如果使用了虚函数,编译器将给每个对象添加一个隐藏成员,这个隐藏成员保存了一个指向函数地址数组的指针。而这个被指向的函数地址数组,我们叫做虚函数表(virtual function table, vtbl)。
    虚函数表存储了为类对象进行申明的虚函数地址。例如基类对象包含一个指针,该指针指向一个装有虚函数地址的数组,这个数组中把每个基类虚函数的地址都存储起来;一个派生类对象也包含一个指针,同样指向装有虚函数地址的数组,这个数组不仅包含了基类的虚函数地址,也包含了派生类的虚函数地址。同时如果派生类对象中虚函数重新定义了,那么派生类的虚函数表中该虚函数的地址被更新;如果派生类中没有对基类虚函数重新定义,那么虚函数表中将存储虚函数原始版本的地址(就是基类版本);如果派生类对象新定义了虚函数,该虚函数地址将被添加到虚函数表中。
    如果还不明白,如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Scientist{
    ...
    char name[40];
    public:
    virtual void show_name();
    virtual void show_all();
    ...
    };
    class Physicist : public Scientist{

    ...
    char field[40];
    public:
    void show_all(); //重定义了基类方法,vtbl将改变原本指向的地址
    virtual void show_field(); //new function
    };

虚函数工作机制