探秘C++虚函数:解密多态的奥秘

虚函数—作为面向对象编程的核心特性之一,虚函数不仅在代码中发挥着重要作用,更是实现多态性的关键所在 。

探秘C++虚函数:解密多态的奥秘

文章插图
什么是虚函数?在 C++ 中,虚函数是为了实现运行时多态性而设计的特殊类型的函数 。通过在基类中声明虚函数,并在派生类中进行重写,可以在程序运行时根据实际对象类型确定调用的函数版本 。这为我们提供了一种灵活的方式来处理继承关系,使得代码更具可扩展性和可维护性 。
虚函数表的作用【探秘C++虚函数:解密多态的奥秘】虚函数表(virtual function table,简称 Vtable)是 实现虚函数的重要机制之一 。每个类(包括含有虚函数的类)都会生成一个对应的虚函数表,其中存储了该类中所有虚函数的地址 。
当对象被创建时,会分配一个指向其类的虚函数表的指针(虚指针) 。通过这个指针 , 程序能够在运行时确定调用的函数版本,实现了动态绑定 。注意与静态绑定混淆重载-静态绑定(链接) 。
虚函数表的性质
  • 每个类都有自己的虚函数表:当一个类中包含至少一个虚函数时,编译器会为该类生成一个虚函数表 。 
  • 虚函数表中存储的是函数指针:虚函数表中的每个条目都是一个指向对应虚函数的函数指针 。 
  • 对象含有指向其类的虚函数表的指针:每个对象都含有一个指向其类的虚函数表的指针,通过这个指针实现动态绑定 。 
派生类的虚函数表包含基类的虚函数表内容,并扩展新函数:派生类的虚函数表通常是在基类的虚函数表的基础上进行扩展的 。 
示例代码解释 让我们通过一段简单的代码来说明虚函数表的工作原理:
#include <IOStream>class Base {public:virtual void func1() {std::cout << "Base::func1()" << std::endl;}virtual void func2() {std::cout << "Base::func2()" << std::endl;}};class Derived : public Base {public:void func1() override {std::cout << "Derived::func1()" << std::endl;}void func3() {std::cout << "Derived::func3()" << std::endl;}};int mAIn() {Base* ptr = new Derived();ptr->func1(); // 动态绑定ptr->func2(); // 动态绑定delete ptr;return 0;}
探秘C++虚函数:解密多态的奥秘

文章插图
在这个示例中 , 我们创建了一个基类 Base 和一个派生类 Derived,后者重写了基类中的 func1() 。
在 main() 函数中,我们创建了一个基类指针指向派生类对象 , 并通过该指针调用了两个虚函数 func1() 和 func2() 。由于 func1() 是虚函数 , 并且对象是 Derived 类型,所以会动态绑定到 Derived::func1() 。而 func2() 在派生类中没有被重写,所以会绑定到基类的版本 。
虚函数表的大小先看一个例子(操作环境64位系统)
//先看空类大小class test {};//只有一个虚函数的类大小class test1 {public:virtual void function(){std::cout << "function()" << std::endl;}};//两个虚函数类的大小class test2 {public:virtual void function1(){std::cout << "function1()" << std::endl;}virtual void function2(){std::cout << "function2()" << std::endl;}};int main(){std::cout<<"sizeof test: "<<sizeof(test)<<std::endl;std::cout<<"sizeof test1: "<<sizeof(test1)<<std::endl;std::cout<<"sizeof test2: "<<sizeof(test2)<<std::endl;return 0;}
探秘C++虚函数:解密多态的奥秘

文章插图
类在内存中记录虚函数是以一个指针记录的,并且该指针指向一个数组,数组中装着的是虚函数的地址 。同时,经过实验,64bit的编译器下,虚函数表的指针大小是8字节 。




    推荐阅读