虚函数和RTTI是 C++ 实现运行时多态的两大核心机制。
1. 虚函数(Virtual Function)
什么是虚函数?
在基类中用virtual声明的成员函数。当通过基类指针或引用调用时,实际执行的是对象真正类型(动态类型)对应的函数版本,而不是指针/引用声明的类型(静态类型)。
核心作用
实现运行时多态(动态绑定)。
简单原理(实现层面)
- 含有虚函数的类会生成一张虚函数表(vtable)。
- 每个对象会多一个隐藏的指针vptr,指向自己类的 vtable。
- 调用虚函数时,通过 vptr 找到正确的函数地址再执行。
示例
#include<iostream>classAnimal{public:virtualvoidspeak()const{// 虚函数std::cout<<"Animal speaks\n";}virtual~Animal()=default;// 强烈建议虚析构};classDog:publicAnimal{public:voidspeak()constoverride{// 重写std::cout<<"Woof!\n";}};classCat:publicAnimal{public:voidspeak()constoverride{std::cout<<"Meow!\n";}};intmain(){Animal*a1=newDog();Animal*a2=newCat();a1->speak();// 输出 Woof!(按动态类型调用)a2->speak();// 输出 Meow!deletea1;deletea2;}重要规则
| 点 | 说明 |
|---|---|
| 虚析构函数 | 通过基类指针delete时,必须虚析构,否则只调用基类析构,导致资源泄漏 |
override | C++11 推荐使用,明确表示重写,防止拼写错误 |
| 纯虚函数 | virtual void f() = 0;使类成为抽象类,不能实例化 |
| 非虚函数 | 始终按静态类型调用,不会动态分派 |
2. RTTI(Runtime Type Information)
什么是 RTTI?
运行时类型信息。允许程序在运行时查询对象的真实类型。
C++ 提供两个主要工具:
typeiddynamic_cast
前提:类型必须是多态类型(至少有一个虚函数),否则typeid和dynamic_cast只能看到静态类型。
2.1typeid
返回const std::type_info&,可用来比较类型。
#include<iostream>#include<typeinfo>Animal*p=newDog();if(typeid(*p)==typeid(Dog)){std::cout<<"这是一只狗\n";}std::cout<<typeid(*p).name()<<'\n';// 实现定义(可能是 "3Dog" 或 "class Dog")注意:typeid对引用和指针解引用后的对象才有效地获取动态类型。
2.2dynamic_cast(安全向下转型)
在运行时检查转型是否合法:
- 成功 → 返回正确的指针/引用
- 失败 → 指针返回
nullptr,引用抛出std::bad_cast
Animal*p=newDog();// 向下转型if(Dog*d=dynamic_cast<Dog*>(p)){d->speak();// 安全使用// 可以调用 Dog 特有的成员}else{std::cout<<"不是 Dog\n";}// 引用版本(失败会抛异常)try{Dog&d=dynamic_cast<Dog&>(*p);}catch(conststd::bad_cast&e){std::cout<<"转型失败\n";}dynamic_cast的常见用途
- 安全地从基类指针转回派生类指针
- 跨继承体系转型(多继承时)
- 判断对象是否属于某个类型
虚函数 vs RTTI 对比
| 特性 | 虚函数 | RTTI(typeid / dynamic_cast) |
|---|---|---|
| 目的 | 调用正确的函数版本 | 查询/检查真实类型 |
| 性能 | 很快(一次间接调用) | 稍慢(需要查类型信息) |
| 依赖 | 虚函数表 | 同样依赖虚函数(多态类型) |
| 使用场景 | 日常多态调用 | 需要知道具体类型时(较少用) |
使用建议
优先使用虚函数
绝大多数多态场景用虚函数就够了,更清晰、更高效。少用 RTTI
频繁使用dynamic_cast或typeid通常是设计不够好的信号(可以用虚函数或 visitor 模式替代)。必须虚析构
只要类可能被基类指针删除,就应该把析构函数声明为虚函数。抽象基类
如果基类不应该被实例化,就把关键接口写成纯虚函数。
一句话总结:
- 虚函数→ 让“调用哪个函数”在运行时决定
- RTTI→ 让“对象到底是什么类型”在运行时可知
两者都依赖“多态类型”(有虚函数),共同支撑 C++ 的运行时多态能力。