当前位置: 首页 > news >正文

C++笔记(面向对象)定义虚函数规则 运行时多态原理

运行时多态的设计思想:


对于相关的类型,确定它们之间的一些共同特征,(属性和方法),将共同特征被转移到基类中,
然后在基类中,把这些共同的函数或方法声明为公有的虚函数接口。然后使用派生类继承基类,并且在派生类中重写这些虚函数,以完成具体的功能。这种设计使得共性很清楚,避免了代码重复,将来容易增强功能,并易于长期维护。
客户端的代码(操作函数)通过基类的引用或指针来指向这些派生类型对象,对虚函数的调用会自
动绑定到派生类对象上重写的虚函数。
虚函数的定义:
虚函数是一个类的成员函数,定义格式如下:
virtual 返回类型 函数名(参数表);
关键字virtual指明该成员函数为虚函数。只能将类的成员函数定义为虚函数。当某一个类的成员函
数被定义为虚函数,则由该类派生出来的所有派生类中,该函数始终保持虚函数的特征。

总结:运行时的多态性: 公有继承 + 虚函数 + (指针或引用调用虚函数)。


定义虚函数的规则

类的成员函数定义为虚函数,但必须注意以下几条:
1. 派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是同名覆盖,不具有多态性。如基类中返回基类指针,派生类中返回派生类指针是允许的,这是一个例外(协变)。
2. 只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象。友元函数和全局函数也不能作为虚函数。
3. 静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。
4. 内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。
5. 构造函数和拷贝构造函数不能作为虚函数。构造函数和拷贝构造函数是设置虚表指针。
6. 析构函数可定义为虚函数,构造函数不能定义虚函数,因为在调用构造函数时对象还没有完成实例化(虚表指针没有设置)。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤消对象时的多态性。
7. 实现运行时的多态性,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现运行时的多态性。
8. 在运行时的多态,函数执行速度要稍慢一些:为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现。所以多态性总是要付出一定代价, 但通用性是一个更高的目标。
9. 如果定义放在类外,virtual只能加在函数声明前面,不能(再)加在函数定义前面。正确的定义必须不包括virtual。

1. 运行时多态的核心:虚函数表(vtable)

1.1 基本概念

当类中包含虚函数时,编译器会为该类生成一个虚函数表(vtable),每个对象会包含一个指向这个表的指针(vptr)。

cpp

class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }virtual void func3() { cout << "Base::func3" << endl; }int base_data;
};class Derived : public Base {
public:void func1() override { cout << "Derived::func1" << endl; }void func2() override { cout << "Derived::func2" << endl; }// 不重写 func3,使用基类的版本int derived_data;
};

1.2 内存布局详解

Base类的内存布局:

text

Base对象:
+----------------+
| vptr           |  --> 指向Base的vtable
+----------------+
| base_data      |
+----------------+Base的vtable:
+----------------+
| &Base::func1   |
+----------------+
| &Base::func2   |
+----------------+
| &Base::func3   |
+----------------+

Derived类的内存布局:

text

Derived对象:
+----------------+
| vptr           |  --> 指向Derived的vtable
+----------------+
| base_data      |  (继承自Base)
+----------------+
| derived_data   |  (Derived自有成员)
+----------------+Derived的vtable:
+-------------------+
| &Derived::func1   |  // 重写的函数
+-------------------+
| &Derived::func2   |  // 重写的函数
+-------------------+
| &Base::func3      |  // 继承基类的函数
+-------------------+

2. 底层实现机制

2.1 编译器如何生成vtable

让我们通过实际代码来观察:

cpp

#include <iostream>
using namespace std;// 用于观察内存布局的辅助类
class MemoryInspector {
public:static void printVTable(void** vptr) {cout << "vtable地址: " << vptr << endl;for (int i = 0; i < 3; ++i) {cout << "  vtable[" << i << "]: " << vptr[i] << " -> 函数地址: " << (void*)vptr[i] << endl;}}
};class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }virtual void func3() { cout << "Base::func3" << endl; }virtual ~Base() {cout << "Base destructor" << endl;}int base_data = 100;
};class Derived : public Base {
public:void func1() override { cout << "Derived::func1" << endl; }void func2() override { cout << "Derived::func2" << endl; }// 不重写func3,使用基类版本int derived_data = 200;
};int main() {Base base;Derived derived;cout << "=== Base对象 ===" << endl;cout << "对象地址: " << &base << endl;cout << "vptr地址: " << (void**)(&base) << endl;MemoryInspector::printVTable(*(void***)(&base));cout << "\n=== Derived对象 ===" << endl;cout << "对象地址: " << &derived << endl;cout << "vptr地址: " << (void**)(&derived) << endl;MemoryInspector::printVTable(*(void***)(&derived));return 0;
}

可能的输出:

text

=== Base对象 ===
对象地址: 0x7ffd4a8b6a10
vptr地址: 0x7ffd4a8b6a10
vtable地址: 0x4022d0vtable[0]: 0x4016aa -> 函数地址: 0x4016aavtable[1]: 0x4016e4 -> 函数地址: 0x4016e4vtable[2]: 0x40171e -> 函数地址: 0x40171e=== Derived对象 ===
对象地址: 0x7ffd4a8b6a20
vptr地址: 0x7ffd4a8b6a20
vtable地址: 0x4022f0vtable[0]: 0x401758 -> 函数地址: 0x401758vtable[1]: 0x401792 -> 函数地址: 0x401792vtable[2]: 0x40171e -> 函数地址: 0x40171e

注意:Derived的vtable中前两个条目指向Derived的重写版本,第三个条目指向Base的func3。


3. 函数调用过程分析

3.1 虚函数调用的底层步骤

cpp

Base* ptr = new Derived();
ptr->func1();  // 这个调用在底层发生了什么?

调用过程的汇编级别分析:

assembly

; 1. 获取对象的vptr
mov rax, qword ptr [ptr]      ; 获取对象地址
mov rax, qword ptr [rax]      ; 获取vptr(对象的前8字节); 2. 通过vptr找到vtable中的函数地址
mov rax, qword ptr [rax]      ; 获取vtable第一个条目(func1的地址); 3. 调用函数
call rax                      ; 间接调用

等效的C++伪代码:

cpp

// ptr->func1() 的实际执行过程:
void*** vptr = (void***)ptr;           // 获取vptr
void* func_address = (*vptr)[0];       // 获取func1的地址
typedef void (*FuncPtr)();             // 函数指针类型
FuncPtr func = (FuncPtr)func_address;  // 转换为函数指针
func();                                // 调用函数

4. 多级继承的vtable结构

4.1 多层继承示例

cpp

class A {
public:virtual void funcA() { cout << "A::funcA" << endl; }int a_data;
};class B : public A {
public:virtual void funcA() override { cout << "B::funcA" << endl; }virtual void funcB() { cout << "B::funcB" << endl; }int b_data;
};class C : public B {
public:virtual void funcA() override { cout << "C::funcA" << endl; }virtual void funcB() override { cout << "C::funcB" << endl; }virtual void funcC() { cout << "C::funcC" << endl; }int c_data;
};

4.2 内存布局分析

C对象的内存布局:

text

C对象:
+----------------+
| vptr           | --> 指向C的vtable
+----------------+
| a_data         |  (来自A)
+----------------+
| b_data         |  (来自B)  
+----------------+
| c_data         |  (来自C)
+----------------+C的vtable:
+----------------+
| &C::funcA      |  // 重写A::funcA
+----------------+
| &B::funcB?     |  // 不对!应该是&C::funcB
+----------------+
| &C::funcC      |  // C的新虚函数
+----------------+

实际验证代码:

cpp

void analyzeHierarchy() {C c;A* a_ptr = &c;B* b_ptr = &c;C* c_ptr = &c;cout << "A* 调用funcA: "; a_ptr->funcA();  // C::funcAcout << "B* 调用funcA: "; b_ptr->funcA();  // C::funcA  cout << "B* 调用funcB: "; b_ptr->funcB();  // C::funcBcout << "C* 调用funcA: "; c_ptr->funcA();  // C::funcAcout << "C* 调用funcB: "; c_ptr->funcB();  // C::funcBcout << "C* 调用funcC: "; c_ptr->funcC();  // C::funcC
}

5. 多重继承的vtable复杂性

5.1 多重继承示例

cpp

class Base1 {
public:virtual void func1() { cout << "Base1::func1" << endl; }int data1;
};class Base2 {
public:virtual void func2() { cout << "Base2::func2" << endl; }int data2;
};class Derived : public Base1, public Base2 {
public:void func1() override { cout << "Derived::func1" << endl; }void func2() override { cout << "Derived::func2" << endl; }virtual void func3() { cout << "Derived::func3" << endl; }int data3;
};

5.2 多重继承的内存布局

text

Derived对象:
+----------------+
| vptr1          | --> 指向Derived/Base1的vtable
+----------------+
| data1          |  (Base1的数据)
+----------------+
| vptr2          | --> 指向Derived/Base2的vtable  
+----------------+
| data2          |  (Base2的数据)
+----------------+
| data3          |  (Derived的数据)
+----------------+vtable1 (对应Base1):
+-------------------+
| &Derived::func1   |
+-------------------+
| &Derived::func3   |  // Derived的新虚函数
+-------------------+vtable2 (对应Base2):
+-------------------+
| &Derived::func2   |
+-------------------+
| 特殊thunk函数?    |  // 可能包含调整this指针的代码
+-------------------+

5.3 指针调整的验证

cpp

void testMultipleInheritance() {Derived d;Base1* b1 = &d;Base2* b2 = &d;cout << "Derived地址: " << &d << endl;cout << "Base1* 地址: " << b1 << endl;  cout << "Base2* 地址: " << b2 << endl;// 两个Base指针的地址不同!// b2的地址 = b1的地址 + sizeof(Base1) + vptr大小
}

6. 性能分析与优化

6.1 虚函数调用开销

虚函数调用的开销主要来自:

  1. 间接内存访问:通过vptr访问vtable

  2. 指令缓存不命中:函数地址不固定

  3. 无法内联:编译时无法确定具体函数

6.2 性能测试对比

cpp

#include <chrono>
using namespace std::chrono;class NonVirtual {
public:void func() { /* 做一些工作 */ }
};class Virtual {
public:virtual void func() { /* 做同样的工作 */ }
};void performanceTest() {const int iterations = 1000000000;NonVirtual nv;Virtual v;Virtual* v_ptr = &v;// 测试非虚函数auto start1 = high_resolution_clock::now();for (int i = 0; i < iterations; ++i) {nv.func();}auto end1 = high_resolution_clock::now();// 测试虚函数auto start2 = high_resolution_clock::now();for (int i = 0; i < iterations; ++i) {v_ptr->func();}auto end2 = high_resolution_clock::now();cout << "非虚函数: " << duration_cast<milliseconds>(end1 - start1).count() << "ms" << endl;cout << "虚函数: " << duration_cast<milliseconds>(end2 - start2).count() << "ms" << endl;
}

7. 虚析构函数的原理

7.1 为什么需要虚析构函数

cpp

class Base {
public:// virtual ~Base() { cout << "Base析构" << endl; }~Base() { cout << "Base析构" << endl; }  // 非虚析构函数
};class Derived : public Base {
public:~Derived() { cout << "Derived析构" << endl; }
};int main() {Base* ptr = new Derived();delete ptr;  // 只调用Base的析构函数!内存泄漏!
}

7.2 虚析构函数在vtable中的位置

text

Base的vtable:
+----------------+
| &Base::~Base   |  // 虚析构函数
+----------------+
| &Base::func1   |
+----------------+Derived的vtable:
+-------------------+
| &Derived::~Derived|  // 完整的析构序列
+-------------------+
| &Derived::func1   |
+-------------------+

8. 总结

运行时多态的核心原理:

  1. vtable(虚函数表):每个包含虚函数的类都有一个虚函数表

  2. vptr(虚函数表指针):每个对象包含指向对应vtable的指针

  3. 动态绑定:通过vptr在运行时查找并调用正确的函数

  4. 继承关系:派生类的vtable基于基类的vtable构建

关键特点:

  • ✅ 灵活性强,支持运行时类型确定

  • ✅ 接口统一,易于扩展

  • ❌ 有性能开销(间接调用)

  • ❌ 内存开销(每个对象多一个vptr)

  • ❌ 无法内联优化

http://www.dtcms.com/a/557054.html

相关文章:

  • 自然语言处理(NLP)之文本预处理:词元化——以《时间机器》文本数据集为例
  • Java-165 Neo4j 图论详解 欧拉路径与欧拉回路 10 分钟跑通:Python NetworkX 判定实战
  • WindowsRE文件夹不显示
  • 【PID】非标准PID控制是否影响控制目标 chapter1(补充)思考
  • 数码和easy
  • 做网站跟app的区别做网站的要求
  • 酷维网站模版wordpress 分类页id
  • MySQL查询一行数据为何变慢?深度排查与优化指南
  • Crashpad介绍
  • 博兴县建设局网站襄阳云平台网站建设
  • 若依分离版前端部署在tomcat刷新404的问题解决方法
  • qcustomplot 显示坐标轴
  • Java Web 项目打包部署全解析:从 IDEA 配置到 Tomcat 运行
  • 如何让网站收录公司名免费网络空间搜索引擎
  • 上海门户网站建设方案河源网络公司
  • WebSocket实战:构建Spring Boot实时聊天应用
  • Go高并发在企业级项目中的实战应用:数据库访问与GIN+GORM深度实践
  • 在网站写小说怎么做封面产品宣传册设计与制作
  • AI学习和研究——环境部署
  • ubuntu中ssh连接root用户
  • (146页PPT)某大型汽车集团企业数字化转型数智化战略规划设计方案(附下载方式)
  • 【Koa.js】 第十课:RESTful API 设计
  • 网站想换个风格怎么做打开网站建设中是什么意思
  • 【26】OpenCV C++实战篇——opencv中 .at<uchar>() 和.ptr<uchar>() 使用方法的区别
  • 2025年10月AGI月评|OmniNWM/X-VLA/DreamOmni2等6大开源项目:自动驾驶、机器人、文档智能的“技术底座”全解析
  • AI训练新纪元:强化学习与LLM深度融合,ChatGPT背后的革命性突破
  • Hudi、Iceberg、Delta Lake、Paimon四种数据湖的建表核心语法
  • 【高阶数据结构】红黑树
  • 许昌网站制作公司百度指数数据分析平台入口
  • 【笔记】解决 ComfyUI 安装 comfy-mtb 节点后 “Face restoration models not found.” 报错