前言

C++多态分为运行时多态和编译时多态,编译时多态的实现主要是模板,重载等,运行时多态就是使用虚函数来实现的

运行时多态

虚函数的底层实现原理是依托于虚函数指针和虚函数表实现的,虚函数指针也是一个成员变量,是编译器自动生成的,存在于实例对象的最前面位置。

虚函数指针工作原理

虚函数指针是一个成员变量,其在构造函数之后才会初始化,所以构造函数是不可以设置成虚函数的。析构函数往往需要设置成虚函数,这是因为如果不设置成虚函数,父类指针指向子类对象删除时,那么只会调用父类析构函数,父类指针无法获取子类的成员函数。

首先,怎么在代码中获取虚函数:

  1. 虚函数指针 vptr 是指向虚函数表的,虚函数每项存储虚函数地址,不妨把虚函数看成是 V_FUN 类型
  2. *vptr + 1表示第 i 个虚函数地址,*(*vptr+i)表示第 i 个虚函数
  3. 所以type(vptr) == V_FUN**
  4. 如何获取vptr呢,一般是对象的前8位(64位系统)或者前4位(32位系统)
  5. 虚函数的表怎么排布的呢?就是按照代码中的先后顺序排布的
#include <iostream>
#include <string>
using namespace std;

class Father {
public:
    virtual string f0() {
        return "Father::f0()";
    }
    virtual string f1() {
        return "Father::f1()";
    }
    virtual string f2() {
        return "Father::f2()";
    }
};

using V_FUN = string (*)();

template<class T>
void run_virtual_function(T t, const int n) {
    auto vptr = reinterpret_cast<V_FUN **>(&t);
    for (int i = 0; i < n; ++i) {
        auto function_i = *(*vptr + i);
        cout << "正在运行第[" << i << "]个虚函数:"
            << function_i() << endl;
    }
}

int main() {
    Father f{};
    run_virtual_function(f, 3);  // 把三个虚函数都运行了一遍
    return 0;
}

虚函数不覆盖继承

如果子类不覆盖父类的虚函数,那么子类的虚函数表就是,拷贝了父类的虚函数表,然后在后面添加自己的虚函数

#include <iostream>
#include <string>
using namespace std;

class Father {
public:
    virtual string f0() {
        return "Father::f0()";
    }
    virtual string f1() {
        return "Father::f1()";
    }
    virtual string f2() {
        return "Father::f2()";
    }
};

class Son: public Father {
    virtual string g0() {
        return "Son::g0()";
    }
    virtual string g1() {
        return "Son::g1()";
    }
    virtual string g2() {
        return "Son::g2()";
    }

};

using V_FUN = string (*)();

template<class T>
void run_virtual_function(T t, const int n) {
    auto vptr = reinterpret_cast<V_FUN **>(&t);
    cout << "虚函数表的地址:" << *vptr << endl;
    for (int i = 0; i < n; ++i) {
        auto function_i = *(*vptr + i);
        cout << "正在运行第[" << i << "]个虚函数:"
            << function_i() << endl;
    }
}

int main() {
    Father f{};
    Son s{};
    cout << "=============== 运行父类的虚函数 ===============" << endl;
    run_virtual_function(f, 3);
    cout << "=============== 运行子类的虚函数 ===============" << endl;
    run_virtual_function(s, 6);
    return 0;
}

虚函数覆盖继承

如果子类虚函数覆盖父类虚函数,那么,会把子类虚函数表中项改变,即指向父类的函数变成指向子类自己的,不管子类对应的函数还是不是虚函数。

#include <iostream>
#include <string>
using namespace std;

class Father {
public:
    virtual string f0() {
        return "Father::f0()";
    }
    virtual string f1() {
        return "Father::f1()";
    }
    virtual string f2() {
        return "Father::f2()";
    }
};

class Son: public Father {
    virtual string g0() {
        return "Son::g0()";
    }
    virtual string g1() {
        return "Son::g1()";
    }
    virtual string g2() {
        return "Son::g2()";
    }
    string f0() override {
        return "Son::f0()";
    }
    string f1() override {
        return "Son::f1()";
    }
    string f2() override {
        return "Son::f2()";
    }
};

using V_FUN = string (*)();

template<class T>
void run_virtual_function(T t, const int n) {
    auto vptr = reinterpret_cast<V_FUN **>(&t);
    cout << "虚函数表的地址:" << *vptr << endl;
    for (int i = 0; i < n; ++i) {
        auto function_i = *(*vptr + i);
        cout << "正在运行第[" << i << "]个虚函数:"
            << function_i() << endl;
    }
}

int main() {
    Father f{};
    Son s{};
    cout << "=============== 运行父类的虚函数 ===============" << endl;
    run_virtual_function(f, 3);
    cout << "=============== 运行子类的虚函数 ===============" << endl;
    run_virtual_function(s, 6);
    return 0;
}

多重继承

多重继承的话,如下所示,加入子类继承了父类和母类,那么就会生成两个虚函数指针,先继承谁的就谁的在前面。然后子类的虚函数拼接在第一个基类的虚函数后面,如果有覆盖,就覆盖掉对应的。

img

img

#include <iostream>
#include <string>
using namespace std;

class Father {
public:
    virtual string f0() {
        return "Father::f0()";
    }
    virtual string f1() {
        return "Father::f1()";
    }
    virtual string f2() {
        return "Father::f2()";
    }
};

class Mother {
public:
    virtual string f0() {
        return "Mother::f0()";
    }
    virtual string f1() {
        return "Mother::f1()";
    }
    virtual string f2() {
        return "Mother::f2()";
    }
};

class Son: public Father, public Mother{
    virtual string g0() {
        return "Son::g0()";
    }
    virtual string g1() {
        return "Son::g1()";
    }
    virtual string g2() {
        return "Son::g2()";
    }
};

using V_FUN = string (*)();

template<class T>
void run_virtual_function(T t, const int n, const int offset) {
    auto vptr = reinterpret_cast<V_FUN **>(&t) + offset;
    cout << "虚函数表的地址:" << *vptr << endl;
    for (int i = 0; i < n; ++i) {
        auto function_i = *(*vptr + i);
        cout << "正在运行第[" << i << "]个虚函数:"
            << function_i() << endl;
    }
}

int main() {
    Father f{};
    Mother m{};
    Son s{};
    cout << "=============== 运行父类的虚函数 ===============" << endl;
    run_virtual_function(f, 3, 0);
    cout << "=============== 运行母类的虚函数 ===============" << endl;
    run_virtual_function(m, 3, 0);
    cout << "=============== 运行子类的父虚函数 ===============" << endl;
    run_virtual_function(s, 6, 0);
    cout << "=============== 运行子类的母虚函数 ===============" << endl;
    run_virtual_function(s, 3, 1);
    return 0;
}

多态原理

经过上述可以看出,为什么使用父类指针可以运行子类的函数了,那是因为,父类指针实际对象还是子类,获取的虚函数指针也还是子类的,顺着虚函数指针去取值,还是会取得子类的虚函数。

PS:但是多重继承中,还是可以通过母类指针来运行母类函数,是因为直接获取地址会自动偏移到母类的虚函数地址。

#include <iostream>
#include <string>
using namespace std;

class Father {
public:
    int* a;  // 增加了一个变量
    virtual string f0() {
        return "Father::f0()";
    }
    virtual string f1() {
        return "Father::f1()";
    }
    virtual string f2() {
        return "Father::f2()";
    }
};

class Mother {
public:
    int* a; // 增加了一个变量
    virtual string f0() {
        return "Mother::f0()";
    }
    virtual string f1() {
        return "Mother::f1()";
    }
    virtual string f2() {
        return "Mother::f2()";
    }
};

class Son: public Father, public Mother{
    virtual string g0() {
        return "Son::g0()";
    }
    virtual string g1() {
        return "Son::g1()";
    }
    virtual string g2() {
        return "Son::g2()";
    }
    // string f0() override {
    //     return "Son::f0()";
    // }
};

using V_FUN = string (*)();

template<class T>
void run_virtual_function(T& t, const int n, const int offset) {
    auto vptr = reinterpret_cast<V_FUN **>(&t) + offset;
    cout << "对象地址:" << &t << endl;
    cout << "虚函数表的地址:" << *vptr << endl;
    for (int i = 0; i < n; ++i) {
        auto function_i = *(*vptr + i);
        cout << "正在运行第[" << i << "]个虚函数:"
            << function_i() << endl;
    }
}

int main() {
    Father f{};
    Mother m{};
    Son s{};
    cout << "=============== 运行父类的虚函数 ===============" << endl;
    run_virtual_function(f, 3, 0);
    cout << "=============== 运行母类的虚函数 ===============" << endl;
    run_virtual_function(m, 3, 0);
    cout << "=============== 运行子类的父虚函数 ===============" << endl;
    run_virtual_function(s, 6, 0);
    cout << "=============== 运行子类的母虚函数 ===============" << endl;
    run_virtual_function(s, 3, 2);  // 需要偏移16个字节(父类虚函数指针,父类的int*)

    // &s 是
    Father* fs = &s;
    Mother* ms = &s;

    cout << "=============== 使用母类运行子类地址的母虚函数 ===============" << endl;
    run_virtual_function(*ms, 3, 0);
    cout << "=============== 使用父类运行子类的父类虚函数 ===============" << endl;
    run_virtual_function(*fs, 3, 0);

    cout << "==============================" << endl;
    cout << "运行父: " << (*fs).f0() << endl;
    cout << "运行母: " << (*ms).f0() << endl;
    cout << "子类地址:" << &s << endl;
    cout << "父指针对象地址:" << &(*fs) << endl;  // 0x7ffdb0028e80
    cout << "母指针对象地址:" << &(*ms) << endl;  // 0x7ffdb0028e90
    return 0;
}

可以看出内存布局是:父类虚函数指针-父类成员变量-母类虚函数指针-母类成员变量

然后如果使用父类或者母类指针指向子类对象,会自动偏移到对应位置,参考0x7ffdb0028e80->0x7ffdb0028e90,就是自动偏移了16 bits