【C++】C++中打印虚函数表内容

一、虚函数的简介

虚函数是 C++ 中实现多态性的重要机制之一。多态性是面向对象编程(OOP)的核心概念之一,它允许程序在执行时根据对象的实际类型调用相应的方法。以下是虚函数与多态性之间关系的详细解释。

多态性的定义
多态性(Polymorphism)允许对象以多种形式出现。在面向对象编程中,主要有两种类型的多态性:

  1. 编译时多态性(静态多态性):

    • 通过函数重载和运算符重载实现。在编译时决定调用哪个函数。
  2. 运行时多态性(动态多态性):

    • 通过虚函数和继承实现。在运行时根据对象的实际类型决定调用哪个函数。

二、虚函数的作用

虚函数 是在基类中声明并在派生类中重写的成员函数。通过将函数声明为虚函数,您可以在基类指针或引用调用该函数时,确保调用的是派生类中实现的函数。

当您通过基类指针或引用调用虚函数时,程序会查找该对象的虚表(vtable)(虚函数指针的数组),并根据对象的实际类型(而不是指针或引用的类型)来调用相应的方法。这种机制就是运行时多态性的体现。但是 分配的时候 虚表是在编译的时候确定的 ,虚表指针是在运行时分配的

三、虚表(vtable)

虚表(vtable)是在编译时确定的,但实际的虚表指针(vptr)是在运行时分配的。让我们详细分析这两个概念及其在程序中的作用。

  1. 定义:

    • 虚表是一个包含指向虚函数实现的指针数组。每个包含虚函数的类都有一个对应的虚表。

    • 当编译器编译类时,它会为每个类生成一个虚表,并在表中存储该类的虚函数的地址。

  2. 编译时确定:

    • 虚表的结构是在编译时确定的,包括它的内容(即虚函数的地址)。因此,编译器能够知道每个类的虚表在运行时的布局。
  3. 单个类的虚表:

    • 如果一个类没有虚函数,它将没有虚表。
    • 如果一个类继承了虚函数,编译器会在类的定义中为该类生成虚表。

四、虚指针(vptr)

  1. 定义:

    • 每个对象(实例)都包含一个指向其虚表的指针,称为虚指针(vptr)。这个指针指向对应类的虚表。
  2. 运行时分配:

    • 当对象被创建时,运行时系统会分配内存,并为对象的 vptr 指针指向相应的虚表。
  • 例如,当创建派生类的对象时,vptr 会指向派生类的虚表,这样可以确保调用到正确的虚函数实现。
    运行时的虚表使用

当通过基类指针或引用调用虚函数时,程序会使用 vptr 来查找实际的虚表,从而找到并调用正确的函数实现。这种机制允许实现多态性。

六、简单总结

  • 虚表:在编译时生成,定义了类的虚函数的实现地址。
  • 虚指针:在运行时为每个对象分配,指向该对象对应的虚表。

示例代码
以下是一个简单的示例,展示了如何使用虚函数实现多态性:

class B1
{
public:
    virtual void Test1()
    {
        std::cout << "B1 test1" << std::endl;
    }
    virtual void Test2()
    {
        std::cout << "B1 test2" << std::endl;
    }

    int b1 = 100;
};
class A : public B1
{
public:
    void Test1() override
    {
        std::cout << "A test1" << std::endl;
    }
    void Test2() override
    {
        std::cout << "A test2" << std::endl;
    }
};

class C : public B1
{
    void Test1() override
    {
        std::cout << "C test1" << std::endl;
    }
    void Test2() override
    {
        std::cout << "C test2" << std::endl;
    }
};
void TestClass(B1 *b)
{
    std::cout << "in TestClass" << std::endl;
    b->Test1();
    b->Test2();
}
int main(int argc, char *argv[])
{
    {
        B1 b;
        b.Test1();
        b.Test2();

        A a;
        a.Test1();
        a.Test2();

        TestClass(&a);

        C c;
        TestClass(&c);
    }
    std::cout << "============================" << std::endl;
    return 0;
    }

输出:

B1 test1
B1 test2
A test1
A test2
in TestClass
A test1
A test2
in TestClass
C test1
C test2
============================

七、多继承虚表的内存布局

/// 虚函数表分析
class B1
{
public:
    virtual void Test1()
    {
        std::cout << "B1 test1" << std::endl;
    }
    virtual void Test2()
    {
        std::cout << "B1 test2" << std::endl;
    }

    int b1 = 100;
};
class B2
{
public:
    virtual void test3()
    {
        std::cout << "B2 test3" << std::endl;
    }

    virtual void test4()
    {
        std::cout << "B2 test4" << std::endl;
    }
    int b2 = 200;
};

class A : public B1
{
public:
    void Test1() override
    {
        std::cout << "A test1" << std::endl;
    }
    void Test2() override
    {
        std::cout << "A test2" << std::endl;
    }
};

class C : public B1
{
    void Test1() override
    {
        std::cout << "C test1" << std::endl;
    }
    void Test2() override
    {
        std::cout << "C test2" << std::endl;
    }
};

void TestClass(B1 *b)
{
    std::cout << "in TestClass" << std::endl;
    b->Test1();
    b->Test2();
}

class D : public B1, public B2
{
public:
    void Test1() override
    {
        std::cout << "D Test1" << std::endl;
    }
    void Test2() override
    {
        std::cout << "D Test2" << std::endl;
    }
    void test3() override
    {
        std::cout << "D test3" << std::endl;
    }
    void test4() override
    {
        std::cout << "D test4" << std::endl;
    }
    int a = 100;
};

求 对象 d1 的大小

int main(int argc, char *argv[])
{
    std::cout << "cppds.com" << std::endl;

    {
        B1 b;
        b.Test1();
        b.Test2();

        A a;
        a.Test1();
        a.Test2();

        TestClass(&a);

        C c;
        TestClass(&c);
    }
    std::cout << "============================" << std::endl;
    /// 多继承各个虚表的获取
    {
        typedef void (**vptr)(); /// 指向虚指针数组的指针
        D d1;
        std::cout << "&d1 = " << reinterpret_cast<unsigned long long>(&d1) << std::endl;
        vptr *vptr1 = reinterpret_cast<vptr *>(&d1); ///
        std::cout << "&(d1.b1) = " << reinterpret_cast<unsigned long long>(&d1.b1)
                  << std::endl; /// 首地址 + 8, 虚表指针就是8字节的 所以加8
        std::cout << "vptr1 = " << reinterpret_cast<unsigned long long>(vptr1) << std::endl; /// 首地址

        auto test1 = **vptr1;
        test1();
        std::cout << " test1 = " << reinterpret_cast<unsigned long long>(test1) << std::endl;

        auto test2 = *(*vptr1 + 1);
        test2();
        std::cout << " test2 = " << reinterpret_cast<unsigned long long>(test2) << std::endl;

        vptr *vptr2 = reinterpret_cast<vptr *>(&d1) + 2; /// 中间隔开了一个变量 8B一格

        std::cout << "vptr2 = " << reinterpret_cast<unsigned long long>(vptr2)
                  << std::endl; /// B2的虚表指针 /// 内存对齐 + 8

        (**vptr2)();
        (*(*vptr2 + 1))();

        std::cout << "&(d1.b2) = " << reinterpret_cast<unsigned long long>(&d1.b2) << std::endl;

        std::cout << "end = " << reinterpret_cast<unsigned long long>(&d1.b2) + 8 << std::endl;

        /// + 8 多一个变量

        std::cout << "sizeof(D) = " << sizeof(D) << std::endl; /// 40
    }
    getchar();
    return 0;
}

输出:

cppds.com
B1 test1
B1 test2
A test1
A test2
in TestClass
A test1
A test2
in TestClass
C test1
C test2
============================
&d1 = 650109777272
&(d1.b1) = 650109777280
vptr1 = 650109777272
D Test1
 test1 = 140702540239033
D Test2
 test2 = 140702540238958
vptr2 = 650109777288
D test3
D test4
&(d1.b2) = 650109777296
end = 650109777304
sizeof(D) = 40
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐