cpp_virtual - ShenYj/ShenYj.github.io GitHub Wiki

虚函数

C++中的多态通过虚函数(virtual function)来实现: 多态:虚函数

汇编代码区别

  • 示例代码

    struct Animal {
        /// 未实现多态
        void speak() {
            cout << "Animal::speak()" << endl;
        }
        /// 实现多态
        virtual void run() {
            cout << "Animal::run()" << endl;
        }
    };
    
    struct Cat : Animal {
    
        override void speak() {
            cout << "Cat::speak()" << endl;
        }
        override void run() {
            cout << "Cat::run()" << endl;
        }
    };
    
    void liu(Animal *p) {
        p->speak();
        p->run();
    }
    
    int main() {
    
        liu(new Cat());
    
        getchar();
        return 0;
    }
  • 汇编层面区别

    /// 非虚函数
            p ->speak();
    call    Animal::speak (函数地址) /// 直接 call 机器码一般 E8 开头
    
    /// 虚函数
            p->run();
    call    eax                    /// 间接 call (调用寄存器中存放的函数地址)  机器码一般 FF 开头

虚表实现原理

虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫虚函数表

使用 sizeof 查看实例对象内存大小会发现实现虚函数后,内存占用会变大 (多一个指针大小空间, 对象的最前面的空间就是存放虚表地址的)

  • 虚表汇编分析

    .

虚表的作用

函数地址和对象绑定,解决了编译期无法明确类型的问题

虚表的细节

  • 不同类型有各自的虚表

  • 所有的 Cat 对象(不管在全局区、栈、堆)共用同一份虚表

  • 如果子类只重写了某一个虚函数,那么虚表中存放着重写后的虚函数地址,同时包括父类虚函数的地址

    • 虚表(x86环境的图)

      .

    创建对象的时候,就已经将对象可能调用到的函数地址(这里仅针对虚函数)存入到虚表内了,没被重写的就直接调用父类的,遵从继承原则(一步到位,不会先找子类、再找父类 OC 的那一套规则)

  • 一个虚函数都没有,就不会有虚表

  • 父类有一个虚函数,派生类重写就是虚函数,重写时 virtual 可省略

  • 当重写虚函数后需要调用父类的成员函数时直接:

    父类类名:: 函数名()
⚠️ **GitHub.com Fallback** ⚠️