首页 > 编程语言 >【C++】C++ 多态的底层实现原理

【C++】C++ 多态的底层实现原理

时间:2024-09-11 22:21:48浏览次数:3  
标签:调用 函数 多态 C++ vptr Print 函数指针 底层

文章目录

1. 多态的定义与作用

多态指的是同一操作在不同对象上具有不同的表现。C++ 中多态分为两类:

  • 编译时多态(静态多态):如函数重载、运算符重载,通过编译器在编译时决定调用哪个函数。
  • 运行时多态(动态多态):主要通过继承和虚函数实现,调用函数时根据实际对象的类型动态决定调用哪个函数。

本文重点讨论 运行时多态,它是通过虚函数机制实现的。

2. 虚函数与虚函数表(vtable)

C++ 中,虚函数 是用 virtual 关键字修饰的成员函数,表示这个函数可以在派生类中被重写,并且可以通过基类指针或引用调用时执行派生类的函数版本。

class Base {
public:
    virtual void Print() {
        std::cout << "Base::Print" << std::endl;
    }
};

class Derived : public Base {
public:
    void Print() override {
        std::cout << "Derived::Print" << std::endl;
    }
};

在上面的例子中,Base 类的 Print 函数是虚函数,Derived 类重写了这个函数。当我们通过基类指针调用 Print 时,会动态决定调用 Base 还是 Derived 的版本。

底层实现上,虚函数的机制依赖于虚函数表(vtable)和虚函数指针(vptr)。

3. 虚函数表(vtable)

每个包含虚函数的类,编译器会为其生成一个 虚函数表(vtable)。虚函数表是一个存放虚函数地址的数组。当对象是某个类的实例时,它会有一个指向该类的虚函数表的指针,称为 虚函数指针(vptr)。

  • 虚函数表 (vtable):每个包含虚函数的类都有一个虚函数表,表中存储的是该类的虚函数指针。如果派生类重写了某个虚函数,虚函数表中对应的指针会指向派生类的版本。
  • 虚函数指针 (vptr):每个对象实例都有一个虚函数指针 vptr,它指向该对象所属类的虚函数表。

当通过基类指针调用虚函数时,编译器根据对象的 vptr 找到对应的 vtable,然后查找表中的虚函数指针并进行调用。这样,实现了动态绑定(运行时多态)。

4. 虚函数调用的底层过程

假设我们有如下代码:

Base* ptr = new Derived();
ptr->Print();

这段代码的底层执行过程如下:

  1. 对象构造:当 Derived 类对象构造时,编译器会在对象内存布局中加入一个 vptr,并将其指向 Derived 类的虚函数表。
  2. 调用虚函数:当 ptr->Print() 被调用时,程序会根据 ptr 指向的对象类型,通过 vptr 查找 Derived 类的虚函数表,找到 Print 函数的地址并进行调用。

虚函数的调用可以简化为以下伪代码:

void (*vptr)() = this->vptr[Print];
vptr();

这里,vptr 指向的是 Derived 类的虚函数表,vptr[Print] 表示从虚函数表中获取 Print 函数的地址,然后通过该地址调用实际的函数。

5. 内存布局中的虚函数指针

为了更好地理解虚函数表在内存中的表现,来看一下对象的内存布局。在一个对象中,虚函数指针通常是对象布局的第一个成员,它指向该对象所属类的虚函数表。

+--------------------+
| vptr (虚函数指针)   |
+--------------------+
| 非静态数据成员      |
+--------------------+

在运行时,当调用虚函数时,程序首先通过对象的 vptr 找到对应类的 vtable,然后查找并调用虚函数。因此,每次调用虚函数都会产生一次间接函数调用开销,这就是为什么虚函数会比普通成员函数稍慢一些的原因。

6. 多重继承中的虚函数表

在 C++ 中,多重继承 会导致一个类拥有多个基类。这时,每个基类都会有自己的虚函数表。为了支持多重继承,编译器会为每个基类维护不同的虚函数表和 vptr

例如:

class Base1 {
public:
    virtual void Func1() {}
};

class Base2 {
public:
    virtual void Func2() {}
};

class Derived : public Base1, public Base2 {
public:
    void Func1() override {}
    void Func2() override {}
};

Derived 类的对象中,它会有两个 vptr,分别指向 Base1Base2 的虚函数表。这种情况下,虚函数调用会更加复杂,因为编译器需要根据继承层次和实际对象类型找到正确的 vtable

7. RTTI 与动态类型识别

C++ 运行时类型识别(RTTI)是通过虚函数表实现的。RTTI 包括 typeiddynamic_cast 等操作,它们可以在运行时检查对象的实际类型。编译器会在虚函数表中增加额外的信息,用于支持类型识别操作。

  • typeid:用于获取对象的类型信息。
  • dynamic_cast:用于安全地将基类指针转换为派生类指针。它依赖于虚函数表中的信息,确保转换的正确性。

标签:调用,函数,多态,C++,vptr,Print,函数指针,底层
From: https://blog.csdn.net/2403_86785171/article/details/142151927

相关文章

  • 递归(c++)
    递归1、斐波那契额数列基础代码#include<iostream>usingnamespacestd;intf(intn){ if(n==1)return1; if(n==2)return2; returnf(n-1)+f(n-2);}intmain(){ intn; cin>>n; cout<<f(n)<<endl; return0;}递归实现指数......
  • Java——多态
    什么是多态:        多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。可能不太懂是什么意思,那首先来简单实现一个:(看效果!!)     classAnamals{Stringname;Stringcolor;public......
  • c++ 数字转化成 string
       ULONG转换成string方法1:使用std::to_string(C++11及更高版本)std::to_string是将数字转换为字符串的简单方式,适用于C++11及更高版本。#include<iostream>#include<string>intmain(){ULONGvalue=1234567890UL;//定义一个ULONG类型的值/......
  • c++ string 转换成 guid
      在C++中,将一个字符串转换为GUID(GloballyUniqueIdentifier)可以通过以下方法实现。GUID通常是128位(16字节)的标识符,以标准格式表示,例如:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx。在C++中,常用的库之一是WindowsAPI,它提供了处理GUID的相关功能。这里是一个示例代码,将字符串转换......
  • 【自用22.】C++类的静态数据成员以及静态成员函数
    需要获取总的人数,如何实现?方案一:使用全局变量,在构造函数中对这个全局变量进行修改具体代码如下:在Human.cpp中定义全局变量,并在构造函数中对人数进行增加操作#include"Human.h"#include<iostream>usingnamespacestd;intHumanCount=0;Human::Human(){ name......
  • C++复习day10
    智能指针为什么需要智能指针?#include<iostream>usingnamespacestd;intdiv(){ inta,b; cin>>a>>b; if(b==0) throwinvalid_argument("除0错误"); returna/b;}voidFunc(){ //1、如果p1这里new抛异常会如何? //2、如果p2这里new抛异常会......
  • C++ web框架:matt-42/lithium
    一、代码示例#include<lithium_http_server.hh>#include<lithium_pgsql.hh>#include"symbols.hh"usingnamespaceli;intmain(){//创建PostgreSQL数据库连接pgsql_databasedb=pgsql_database(s::host="localhost"......
  • C++中的数组,字符串数组,pair数组
    1.C++中的字符串数组: 2.C++中的常量数组 这个constpair<int,string>valueSymbols[]定义了一个常量数组,数组中的每个元素都是一个pair<int,string>类型的对象。pair是C++标准模板库(STL)中的一个模板类,用于将两个值组合成一个单一的对象。在这个特定的例子中,pair的第一个......
  • C++入门教程:第八篇 - 文件I/O操作
    C++入门教程:第八篇-文件I/O操作文件I/O(输入/输出)是程序与外部存储设备进行数据交换的关键操作。在C++中,文件I/O操作由标准库提供的流类完成。通过这些流类,程序可以读写文件,处理文件内容。本文将介绍C++中的文件I/O基础,包括如何打开、读写和关闭文件。1.文件流基础C++提......
  • C++ 虚析构函数简单测试
    classBase{public:virtual~Base(){cout<<"~Base"<<'\n';}};classDerived:publicBase{public:~Derived(){cout<<"~Derived"<<'\n';}};intmain(){{......