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