虚函数和 RTTI实现运行时多态

虚函数 和 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++ 提供两个主要工具:

  1. typeid
  2. dynamic_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)
目的 调用正确的函数版本 查询/检查真实类型
性能 很快(一次间接调用) 稍慢(需要查类型信息)
依赖 虚函数表 同样依赖虚函数(多态类型)
使用场景 日常多态调用 需要知道具体类型时(较少用)

使用建议

  1. 优先使用虚函数

    绝大多数多态场景用虚函数就够了,更清晰、更高效。

  2. 少用 RTTI

    频繁使用 dynamic_cast 或 typeid 通常是设计不够好的信号(可以用虚函数或 visitor 模式替代)。

  3. 必须虚析构

    只要类可能被基类指针删除,就应该把析构函数声明为虚函数。

  4. 抽象基类

    如果基类不应该被实例化,就把关键接口写成纯虚函数。


一句话总结:

  • 虚函数 → 让"调用哪个函数"在运行时决定
  • RTTI → 让"对象到底是什么类型"在运行时可知

两者都依赖"多态类型"(有虚函数),共同支撑 C++ 的运行时多态能力。

相关推荐
2501_9445215915 小时前
Ramda 跨端三方库仓颉适配全流程
开发语言·javascript·flutter·php
行思理16 小时前
go语言学习难度及适合学习的人群
开发语言·学习·golang
青瓦梦滋16 小时前
WebSocket协议
linux·服务器·网络·c++·websocket·网络协议·http
wangchunyu11417 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python
程序 代码狂人18 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法
meiying883819 小时前
快递单号批量查询Python脚本:从手动到自动化的完整实现
开发语言·python·自动化
@大迁世界20 小时前
告别 Go,拥抱 Rust
开发语言·golang·rust
SEO_juper20 小时前
Node.js 重定向链检测工具:301 跳了几跳、有没有成环,一次看清
xml·开发语言·python·node.js·json·bash
学逆向的20 小时前
C 指针常量与常量指针
c语言·开发语言·网络安全
低调滴开发21 小时前
3 行代码写崩一个函数?切片才是 Rust 字符串处理的正确姿势
开发语言·后端·rust