953 字
5 分钟
12虚函数表的内存管理
2025-02-15

虚函数表的内存管理#

class Base{
private:
int data;
public:
virtual void f1();
virtual void f2();
virtual void f3();
};
class Derived{
private:
char* name;
public:
virtual void f1() {std::cout << "derived f1()" << std::endl } // 重载
virtual void g1();
virtual void h1();
};

类的内存结构#

类的内存结构如下:

  1. 虚函数表地址
  2. 各个成员变量
  3. …(成员变量)

例如上面代码中,Base对象的内存大小为16,主要包括:

  1. 虚函数表指针,8Bytes
  2. int数据,4Bytes
  3. 内存对齐,4Bytes

虚函数表的结构#

成员函数并不直接存储在类所属的内存中,而是单独开辟一块内存空间(叫做虚函数表)存储。

虚函数表中存储的函数,以函数指针的形式存在。需要对函数指针解引用,才能调用函数。

多态和虚函数表#

每个类都有自己的虚函数表,Derived类继承自Base类后,会给自己开辟一块虚函数表空间,存储自己的成员函数。

Base类有自己的虚函数表,内部存储成员函数的函数指针。

Derived类的虚函数表中,存在三种函数,对应三种指针。

  1. 被重写的函数,指向重写函数。
  2. 继承自Base的函数,指向基类的函数。
  3. 类自己的成员函数,指向自己的成员函数。

多态#

Derived d();
Base* bp = &d;
bp->f1(); // 调用Derived的版本

bp是指向Derived对象的指针,同时指向了Derived类的虚函数表。

当调用f1()函数时,根据bp找到Derived的虚函数表,从而在虚函数表中寻找f1()函数对应的指针。

因为Derived虚函数表中的函数指针指向重写后的版本,因此调用的是重写后的f1()函数

多重继承下的多态#

在多重继承下,派生类会保存多个虚函数表,每个虚函数表对应一个基类。

并且,派生类未重写的成员函数会默认存储在第一个Vtable中。

虚继承和菱形继承#

如果存在菱形继承情况,类A中的成员变量/函数会在D中重复声明,存在二义性问题。

此时就需要使用虚继承来解决。

class A {
public:
virtual void func() { cout << "A::func" << endl; }
virtual ~A() = default;
};
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {
public:
void func() override { cout << "D::func" << endl; }
};
int main() {
D d;
A* p = &d;
p->func(); // 调用 D::func()
}

在虚继承情况下,BC共享A的唯一实例,所有的基类共享一个vtable,因此即使使用多态,仍然访问D::func()

协变返回类型#

协变返回类型(Covariant Return Type),属于函数重写相关的概念,当一个虚函数的派生类重写基类的虚函数时,使用协变返回类型能够使派生类重写的函数,返回更具体地子类类型。

#include <iostream>
using namespace std;
class Parent {
public:
virtual Parent* getInstance() {
return new Parent();
}
};
class Child : public Parent {
public:
Child* getInstance() override { // 返回更具体的子类类型
return new Child();
}
};
int main() {
Child c;
Child* newObj = c.getInstance(); // 直接获得 Child 类型,无需转换
delete newObj;
return 0;
}

如果不满足虚函数的情况,那么当使用基类指针调用函数时,返回子类版本的函数会被隐藏,导致协变返回类型失效。

#include <iostream>
using namespace std;
class Parent {
public:
Parent* getInstance() { // ❌ 不是 virtual 函数
return new Parent();
}
};
class Child : public Parent {
public:
Child* getInstance() { // ❌ 不是 override,而是隐藏
return new Child();
}
};
int main() {
Parent* p = new Child();
Parent* obj = p->getInstance(); // 调用的是 Parent::getInstance()
cout << "Type: " << typeid(*obj).name() << endl; // 仍然是 Parent,而不是 Child
delete obj;
delete p;
return 0;
}
12虚函数表的内存管理
https://chrisnake11.github.io/blog/posts/coding/c基础/12虚函数表的内存管理/
作者
Zheyv
发布于
2025-02-15
许可协议
CC BY-NC-SA 4.0