c++ 虚函数表 virtual method table (VMT)

虚函数表

c++重写父类的函数必须是虚函数吗?

  • 如果父类的函数不是虚函数,子类也可以重新声明一个同名的函数,但这只是隐藏了父类的函数,而非重写。
  • 重写是子类对父类的方法进行覆盖,子类继承父类的方法后可以根据需要进行重写;重载是在同一个类中定义多个方法,方法名相同但参数列表不同。

虚函数表是什么?

  • 虚函数表可以看作是一个类的静态成员,它存储了该类所有虚函数的地址。C++虚函数表为什么不设计成静态成员的形式?

  • 在C++中,虚函数是通过虚函数表(Virtual Function Table)来实现的。

  • 当一个类中存在虚函数时,编译器会为该类生成一个虚函数表。每个类对象都包含一个指向虚函数表的指针(通常被称为虚函数指针或vptr),这个指针存在于对象的内存布局中。当对象被创建时,vptr会被初始化为指向该类的虚函数表。

Layer 1 栈区 堆区 静态, 全局变量,常量 代码 成员变量 虚表指针 虚函数f的地址 虚函数g的地址 实例化的类对象存储在堆或栈上: 成员函数存储在代码区
  • 当通过对象调用虚函数时,编译器会通过对象的vptr找到对应的虚函数表,并根据虚函数的索引(通常是函数在虚函数表中的位置)来调用相应的虚函数。这就是动态绑定(Dynamic Binding)的机制,它能够在运行时确定对象的具体类型,并调用相应的虚函数,而不是在编译时就确定调用的函数。

  • 注:内存结构类似金字塔的结构,地址越低越稳定,地址越高对程序的影响越大(栈区之上为linux系统内核空间)

c++如何通过虚函数表重写父类的虚函数?

  • 类 B 继承于类 A,类 B 可以调用类 A 的函数,类B的虚函数表中会包含所有类A的地址。
  • 如果类B重写了类A的虚函数,则类B的虚函数表中对应位置的虚函数地址会改变

c++ 中类对象的指针是如何知道自己的访问边界的?

  • 在C++中,类对象的指针并不直接知道自己的访问边界。指针本身只存储了对象的内存地址,不包含关于对象大小或边界的信息。

  • 类对象的访问边界是由编译器在编译时确定的,编译器会根据类的定义计算对象的大小,并在对象的内存布局中添加用于管理对象的内部操作的元数据。这些元数据包括虚函数表指针、虚基类指针等。

  • 所以当我们使用以下代码时D DrivObject; B *p = & DrivObject; p只能访问基础对象的所有成员变量,成员函数和虚表指针(所有虚函数被一个虚表指针代替了),虚表指针属于基类部分,所以可以用p访问D中重写的函数。

如果基类析构函数没有声明为虚函数会造成什么问题?

  • 虚函数表实在对象构造之后才建立的,所以构造函数不可能是虚函数,且不能在构造函数内调用虚函数
    若析构函数不是虚函数,delete 时,只有基类会被释放,而子类没有释放,存在内存泄漏的隐患。(因为代码中没有子类的对象,只有基类的指针。)
  • 代码例子:
#include 

class Base {
public:
    int baseValue;

    Base(int value) : baseValue(value) {}
    virtual ~Base(){std::cout << "~Base()"<< std::endl;}
    
    virtual void ShowValue() {
        std::cout << "Base Value: " << baseValue << std::endl;
    }
};

class Derived : public Base {
public:
    int derivedValue;

    Derived(int base, int derived) : Base(base), derivedValue(derived) {}
    ~Derived(){std::cout << "~Derived()"<< std::endl;}

    void ShowValue() {
        std::cout << "Derived Value: " << derivedValue << std::endl;
    }
};

int main() {
    // Derived derivedObj(1, 100);
    
    Base* basePtr = new Derived(1, 100); // 子类指针对象赋值给父类指针

    basePtr->ShowValue(); // 调用的是父类的 ShowValue(),不是子类的版本 ,  Base Value: 1
    
    delete basePtr;

    return 0;
}

在c++的多态中,为什么虚析构函数会逐层调用而其他重写的函数不会?

        这是因为类的创建和虚构是“逐层”进行的:

  • 在类的创建过程中,如果该类继承自其他类,则会先调用父类的构造函数,以确保父类的属性和状态也被正确初始化。这个过程会一直进行下去,直到达到类层次结构的顶层。

  • 类的虚构与类的创建相反,它是在销毁一个类的实例时进行的。当一个类的实例不再被使用时,会调用该类的析构函数来释放对象所占用的资源。析构函数会按照与构造函数相反的顺序逐层调用,先调用子类的析构函数,然后再调用父类的析构函数,直到达到类层次结构的顶层。

CG

  • 不同场景下的 C++ 对象内存布局

你可能感兴趣的:(语言学习笔记,c++,java,jvm)