C++虚函数表

虚函数表(Virtual Table,简称 vtable)是 C++ 中实现 多态性 的关键机制之一。它用于支持类的虚函数的动态绑定(也叫运行时多态)。通过虚函数表,C++ 可以在程序运行时动态地调用不同子类的函数,而不是在编译时决定调用哪个函数。

以下是虚函数表的详细介绍,包括其工作原理和常见的面试问题。


1. 虚函数的概念

在 C++ 中,虚函数(virtual function)是指在基类中声明为 virtual 的函数,其目的是在派生类中可以进行重写(override)。虚函数可以让基类的指针或引用在运行时动态调用派生类的实现,而不是基类的实现。这样可以实现多态性。

class Base {
public:
    virtual void show() {
        std::cout << "Base class show()" << std::endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        std::cout << "Derived class show()" << std::endl;
    }
};

在上面的例子中,Base 类的 show() 是虚函数,Derived 类重写了这个函数。如果我们通过 Base 类的指针指向 Derived 类的对象,调用 show() 时,会动态地调用 Derived 类的 show() 实现,而不是 Base 类的实现。


2. 虚函数表的概念

虚函数表(vtable)是编译器为每个含有虚函数的类自动生成的一个指针表,它记录了与该类相关的虚函数指针。虚函数表通过对象的 虚指针(vptr)来实现函数的动态调度。

每个含有虚函数的类都有一个虚函数表:

  • 虚函数表中存储的是该类中所有虚函数的函数指针
  • 对象的每一个实例都包含一个指向该虚函数表的指针,称为虚指针(vptr)
虚函数表的工作原理
  1. 当一个类包含虚函数时,编译器会为这个类生成一个虚函数表 vtable
  2. 每个对象的内存布局中会多一个虚指针 vptr,它指向该类的虚函数表。
  3. 虚函数表中每个槽位存储着指向该类虚函数的函数指针。
  4. 当调用虚函数时,程序首先通过对象的 vptr 查找虚函数表,定位到相应的函数指针,并调用该函数。
虚函数表的例子
class Base {
public:
    virtual void show() {
        std::cout << "Base class show()" << std::endl;
    }

    virtual void display() {
        std::cout << "Base class display()" << std::endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        std::cout << "Derived class show()" << std::endl;
    }
};

在这个例子中:

  • Base 类有两个虚函数:show()display()
  • Derived 类重写了 show() 函数。

生成的虚函数表可能如下:

  • Base 类的虚函数表 (Base::vtable)

    函数序号函数指针
    0Base::show
    1Base::display
  • Derived 类的虚函数表 (Derived::vtable)

    函数序号函数指针
    0Derived::show
    1Base::display

当调用 show() 时,如果对象是 Base 类的实例,vptr 会指向 Base::vtable,因此会调用 Base::show()。而如果对象是 Derived 类的实例,vptr 会指向 Derived::vtable,因此会调用 Derived::show()display() 没有在 Derived 类中重写,因此无论是 Base 对象还是 Derived 对象,都调用 Base::display()


3. 虚指针(vptr)的概念

虚指针(vptr)是编译器为每个对象自动生成的一个指针,它指向对象所属类的虚函数表。每个对象都有自己的虚指针,但是指向同一个虚函数表。

  • 当一个对象创建时,编译器会在对象的内存布局中插入一个指向虚函数表的指针 vptr
  • 当调用虚函数时,程序通过 vptr 找到该对象所属的类的虚函数表,再通过表中的函数指针调用实际的函数。

4. 虚函数表和多继承

在多继承的情况下,C++ 可能会为类创建多个虚函数表。例如:

class Base1 {
public:
    virtual void func1() { std::cout << "Base1::func1" << std::endl; }
};

class Base2 {
public:
    virtual void func2() { std::cout << "Base2::func2" << std::endl; }
};

class Derived : public Base1, public Base2 {
public:
    void func1() override { std::cout << "Derived::func1" << std::endl; }
};

在这种情况下,Derived 类可能会有两个虚函数表,一个是用于 Base1 的虚函数,另一个是用于 Base2 的虚函数。


5. 虚函数表的内存布局

对象的内存布局在含有虚函数时,会发生以下变化:

  • 对象的开头处会增加一个指向虚函数表的指针 vptr
  • vptr 指向虚函数表,该表中记录了该类的虚函数的函数指针。
  • 如果类不含有虚函数,内存中就没有 vptr

6. 常见面试问题

(1) 什么是虚函数表?
  • 虚函数表(vtable)是编译器在实现虚函数(virtual function)时生成的一个表格,用于存储类中所有虚函数的函数指针。
  • 每个含有虚函数的类都有一个虚函数表,虚函数表的每一项是虚函数的入口地址。
(2) 虚函数表是如何实现多态的?
  • 当基类指针或引用指向派生类对象时,程序通过虚指针(vptr)找到对应的虚函数表,然后根据虚函数表中的函数指针调用派生类中重写的虚函数,从而实现多态性。
  • 动态绑定通过虚函数表实现,它允许在运行时根据实际对象类型调用对应的虚函数。
(3) 虚函数表在多继承中的实现?
  • 在多继承的情况下,编译器通常为每个基类创建一个虚函数表,因此派生类的对象可能包含多个虚指针,每个指针指向各自基类的虚函数表。
(4) 虚函数和普通函数的区别?
  • 普通函数是在编译时确定调用哪个函数,而虚函数是在运行时通过虚函数表动态确定调用哪个函数。
  • 普通函数没有虚指针和虚函数表的开销,而虚函数需要通过虚指针找到对应的虚函数表,增加了调用的间接性。
(5) 虚函数表的性能开销?
  • 虚函数表的使用引入了一定的开销,因为每次调用虚函数时,必须通过虚指针查找虚函数表,再调用相应的函数。这增加了一次间接寻址操作。
  • 此外,虚指针占用了一定的内存,每个对象会多出一个指针的空间来存储虚指针。
(6) 如何避免虚函数的开销?
  • 如果不需要运行时的多态行为,可以避免使用虚函数。例如,当类的行为不需要通过继承和重写来扩展时,可以不使用虚函数,这样可以避免虚函数表和虚指针的开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值