C++ 多态和虚函数表

1 多态

c++ 中,多态是面向对象编程的核心特性之一,c++ 允许使用基类的指针调用子类重写的方法,从而实现同一个接口有不同的行为。

在基类中定义函数 virtual void Speak();,virtual 表示为虚函数,可以被继承的子类函数重写。

在子类中定义函数 void Speak() override;,表示重写了基类的 Speak 函数。

比如下面这个例子

#include <set>
#include <iostream>

class Animal {
public:
    virtual void Speak() = 0;
};

class Human : public Animal {
    void Speak() override {
        std::cout << "hello" << std::endl;
    }
};

class Dog : public Animal {
    void Speak() override {
        std::cout << "wangwangwang" << std::endl;
    }
};

class Sheep : public Animal {
    void Speak() override {
        std::cout << "miemiemie" << std::endl;
    }
};

int main() {
    std::set<Animal*> animals;
    animals.insert(new Human);
    animals.insert(new Dog);
    animals.insert(new Sheep);
    
    for (auto& animal : animals) {
        animal->Speak();
    }
    return 0;
}

2 虚函数表

虚函数表 vtable 是 c++ 实现多态的关键技术,在每个存在继承关系的类的对象中,最前面一个指针就是虚函数表,gdb 打印对象可以看到,vptr.Animal 指针指向的就是类的虚函数表。x类的虚函数在虚函数表中依次排列。

2.1 没有虚函数的类没有虚函数表

比如,下面的例子,gdb打印对象可以看到没有虚函数表

class Car {
public:
    void Speak() {
        std::cout << "bbbb" << std::endl;
    }
};

int main() {
    Car c;
    c.Speak();
    return 0;
}

2.2 虚函数表是一个函数指针的数组,对象的虚函数位于这个数组中

#include <iostream>

class Animal {
public:
    virtual void Speak() = 0;
    virtual void Run() = 0;
};

class Human : public Animal {
public:
    void Speak() override {
        std::cout << "hello" << std::endl;
    }

    void Run() override {
        std::cout << "human run" << std::endl;
    }
};

class Dog : public Animal {
    void Speak() override {
        std::cout << "wangwangwang" << std::endl;
    }

    void Run() override {
        std::cout << "dog run" << std::endl;
    }
};

class Sheep : public Animal {
    void Speak() override {
        std::cout << "miemiemie" << std::endl;
    }

    void Run() override {
        std::cout << "sheep run" << std::endl;
    }
};

int main() {
    Animal* h = new Human;
    Animal* d = new Dog;
    Animal* s = new Sheep;

    printf("Human::Speak: %p Human::Run %p\n", (void*)&Human::Speak, (void*)&Human::Run);

    return 0;
}

gdb 可以看到 Human类实现的虚函数 Speak 0x40127e 和 Run 0x4012aa 位于 vtable 中

2.3 虚函数表中只有虚函数

class Animal {
public:
    virtual void Speak() = 0;
    virtual void Run() = 0;
};

class Human : public Animal {
public:
    void Speak() override {
        std::cout << "hello" << std::endl;
    }

    void Speak1() {
    }

    void Run() {
        std::cout << "human run" << std::endl;
    }
};

int main() {
    Animal* h = new Human;

    printf("Human::Speak: %p Hunam::Speak1 %p Human::Run %p\n", (void*)&Human::Speak, (void*)&Human::Speak1, (void*)&Human::Run);

    return 0;
}

gdb可以看到 Human 虚函数表中只有重写的基类虚函数 Speak 和 Run,而函数 Speak1 由于不是虚函数所以不在虚函数表中

2.4 如果两个对象是同一个子类,那么虚函数表的指针相同

不同的子类虚函数表不同,但同一个子类,每个对象的虚函数表都是一个

Human* h = new Human;
Human* h2 = new Human;

最后更新于