C++ 虚函数、多态与绑定机制
在 C++ 中,对象本身只保存数据和一个隐藏的虚表指针(vptr),而所有成员函数的代码都只存在于程序的代码段中。
如果类有虚函数,编译器会为每个类生成一张虚表(vtable),虚表里存放对应虚函数的函数地址。
当创建Derived对象时,对象内的vptr会指向Derived::vtable;如果用Base*指向这个对象并调用虚函数,程序会在运行时通过vptr → vtable → 函数地址,最终调用到Derived::f,这就是动态绑定。
如果是普通对象或按值传递(发生对象切片),就不会经过虚表,而是在编译期直接决定调用Base::f,这是静态绑定。
构造和析构过程中,vptr会随着当前构造层级切换,确保虚函数只调用“当前已构造完成”的那一层实现。
引言:为什么需要多态?
设想一个图形绘制系统:
classShape{ public:voiddraw(){ /* 绘制基础形状 */}};classCircle:publicShape{ public:voiddraw(){ /* 绘制圆形 */}};classRectangle:publicShape{ public:voiddraw(){ /* 绘制矩形 */}};// 问题来了:如何统一处理?voiddrawAll(Shape* shapes[],int count){ for(int i =0; i < count; i++){ shapes[i]->draw();// 期望:调用实际类型的 draw}}核心问题:如何让基类指针调用到正确的派生类函数?
这就引出了多态(Polymorphism)的需求,而实现多态的关键是绑定机制。
一、什么是“绑定(Binding)”
绑定指的是:
在某个时间点,确定“一次函数调用”究竟对应哪一个具体函数实现。
在 C++ 中,绑定主要分为两类:
- 静态绑定(Static Binding / Early Binding)
- 动态绑定(Dynamic Binding / Late Binding)
二、静态绑定(Static Binding)
1️⃣ 定义
在编译期就能确定调用目标的绑定方式。
2️⃣ 典型特征
- 不依赖对象的运行时类型
- 调用目标在编译期已确定
- 通常可被内联,性能最好
3️⃣ 发生静态绑定的情况
(1)非 virtual 函数
structBase{ voidf(){ std::cout <<"Base::f\n";}};structDerived:Base{ voidf(){ std::cout <<"Derived::f\n";}}; Base* p =new Derived; p->f();// 静态绑定,调用 Base::f,输出 "Base::f"解析:
- 编译器看到
p类型是Base* f()不是虚函数,编译期直接确定调用Base::f- 即使
p实际指向Derived对象,也不会调用Derived::f
(2)按值调用(对象切片)
voidprocess(Base b){ // 按值传递 b.f();// 始终调用 Base::f} Derived d;process(d);// 静态绑定,Base::f切片过程:
- 传参时构造一个新的
Base对象 - 只拷贝
Derived中的Base部分 Derived的额外数据丢失- vptr 被重置为
Base::vtable
(3)构造 / 析构期间的虚函数调用
structBase{ Base(){ f();}// 在构造函数中调用virtualvoidf(){ std::cout <<"Base::f\n";}};structDerived:Base{ voidf()override{ std::cout <<"Derived::f\n";}}; Derived d;// 输出 "Base::f",不是 "Derived::f"- 即使
f是 virtual - 构造期间仍 静态绑定到当前构造层级
4️⃣ 静态绑定的性能优势
// 编译器可能的优化inlinevoidBase::f(){ /* ... */} p->f();// 静态绑定 → 可内联展开 → 无函数调用开销- 零运行时开销
- 可被内联优化
- 无需查表操作
三、动态绑定(Dynamic Binding)
1️⃣ 定义
在运行时,根据对象的动态类型决定调用哪个函数实现。
2️⃣ 动态绑定的三个必要条件
- 函数被声明为
virtual - 通过 基类指针或引用 调用
- 指针/引用指向 完整的派生类对象
structBase{ virtualvoidf(){ std::cout <<"Base::f\n";}};structDerived:Base{ voidf()override{ std::cout <<"Derived::f\n";}}; Base* p =new Derived; p->f();// 动态绑定,输出 "Derived::f" Base& r =*new Derived; r.f();// 动态绑定,输出 "Derived::f"3️⃣ 完整示例:对比静态与动态绑定
#include<iostream>structBase{ voidnormalFunc(){ std::cout <<"Base::normalFunc\n";}virtualvoidvirtualFunc(){ std::cout <<"Base::virtualFunc\n";}};structDerived:Base{ voidnormalFunc(){ std::cout <<"Derived::normalFunc\n";}voidvirtualFunc()override{ std::cout <<"Derived::virtualFunc\n";}};intmain(){ Derived d