C++ 虚函数与多态详解
这是 C++ 面向对象编程中最核心的特性 :虚函数(Virtual Function)和多态(Polymorphism)。让我详细解释:
🎯 核心概念:运行时多态
1. 基础语法结构
cpp
// 接口类(抽象基类)
class ImageEditorProcessorListener {
public:
// 纯虚函数 (= 0 表示必须被子类实现)
virtual void onGetImageList(const std::string& id, std::string list, std::string host) = 0;
};
// 实现类1
class ImageEditorManager : public ImageEditorProcessorListener {
public:
// override 关键字表示重写父类虚函数
void onGetImageList(const std::string& id, std::string list, std::string host) override {
// 具体实现...
}
};
// 实现类2
class ImageListener : public IImageEditorListener {
public:
void onGetImageList(const std::string& id, std::string list, std::string host) override {
// 另一个实现...
}
};
🔍 为什么会跳转?关键在于指针/引用的实际类型
第一次跳转:从 ImageEditorProcessor 到 ImageEditorManager
cpp
// 在 ImageEditorProcessor.h 中
private:
std::shared_ptr<ImageEditorProcessorListener> mImageEditorProcessorListener;
// ↑ 声明类型是基类指针
cpp
// 在 ImageEditorManager.h 的 init() 方法中
void init() {
imageEditorProcessor = std::make_shared<ImageEditorProcessor>();
imageEditorProcessor->init();
// 关键代码:传入 ImageEditorManager 的 shared_ptr
imageEditorProcessor->setFileProcessorListener(shared_from_this());
// ↑ 实际类型是 ImageEditorManager
}
cpp
// setFileProcessorListener 的实现
void setFileProcessorListener(std::shared_ptr<ImageEditorProcessorListener> listener) {
mImageEditorProcessorListener = listener;
// 现在 mImageEditorProcessorListener 指向的是 ImageEditorManager 对象
}
关键点:
mImageEditorProcessorListener的声明类型 是ImageEditorProcessorListener*(基类指针)- 但它的实际指向 是
ImageEditorManager对象(派生类对象)
虚函数表(VTable)机制
当调用 mImageEditorProcessorListener->onGetImageList(...) 时:
markdown
编译器的处理流程:
1. 检查 mImageEditorProcessorListener 指向的对象的 vtable(虚函数表)
2. 在 vtable 中查找 onGetImageList 的实际实现地址
3. 发现实际对象是 ImageEditorManager
4. 跳转到 ImageEditorManager::onGetImageList()
虚函数表示意图:
cpp
ImageEditorProcessorListener 的 vtable:
├─ onGetImageList → [纯虚函数,无实现]
├─ onPreviewImage → [纯虚函数,无实现]
└─ ...
ImageEditorManager 的 vtable:
├─ onGetImageList → 指向 ImageEditorManager::onGetImageList 的地址
├─ onPreviewImage → 指向 ImageEditorManager::onPreviewImage 的地址
└─ ...
🔄 第二次跳转:从 ImageEditorManager 到 ImageListener
cpp
// ImageEditorManager.h
void onGetImageList(const std::string& id, std::string imageList, std::string host) override {
std::lock_guard<std::mutex> lock(fileListenerMutex);
// 遍历监听器列表
for (const auto& listener : imageEditorListenerList) {
// ↑ listener 的声明类型是 IImageEditorListener*
// 实际类型是 ImageListener*
listener->onGetImageList(id, imageList, host);
// 再次触发虚函数机制,跳转到 ImageListener::onGetImageList()
}
}
cpp
// 在某处注册监听器
private:
std::vector<std::shared_ptr<IImageEditorListener>> imageEditorListenerList;
// ↑ 基类指针容器
cpp
// 添加监听器时(实际是 ImageListener 对象)
imageEditorManager->addFileListener(std::make_shared<ImageListener>());
// ↑ 派生类对象,但存储为基类指针
📚 核心语法知识点总结
1. 虚函数(virtual)
cpp
class Base {
public:
virtual void func() { } // 虚函数
};
- 使用
virtual关键字声明 - 允许派生类重写(override)
- 支持运行时多态
2. 纯虚函数(= 0)
cpp
class Interface {
public:
virtual void func() = 0; // 纯虚函数
};
- 使用
= 0语法 - 必须被派生类实现
- 包含纯虚函数的类是抽象类,不能实例化
3. override 关键字(C++11)
cpp
class Derived : public Base {
public:
void func() override { } // 明确表示重写父类虚函数
};
- 编译器会检查是否真的重写了父类虚函数
- 防止拼写错误或参数不匹配
4. 多态的三个条件
✅ 必须满足:
- 有继承关系
- 子类重写父类的虚函数
- 通过基类指针或引用调用虚函数
cpp
Base* ptr = new Derived(); // 基类指针指向派生类对象
ptr->func(); // 调用的是 Derived::func(),而非 Base::func()
🎬 完整执行流程图
css
调用点:
mImageEditorProcessorListener->onGetImageList(deviceId, listStr, host)
↓
[运行时检查] mImageEditorProcessorListener 指向什么对象?
↓
指向 ImageEditorManager 对象
↓
通过 vtable 跳转到 ImageEditorManager::onGetImageList()
↓
for (listener : imageEditorListenerList) {
↓
[运行时检查] listener 指向什么对象?
↓
指向 ImageListener 对象
↓
通过 vtable 跳转到 ImageListener::onGetImageList()
↓
执行具体业务逻辑(下载图片、更新UI等)
}
💡 为什么这样设计?
优点:
- 解耦 :
ImageEditorProcessor不需要知道具体的实现类 - 扩展性:可以轻松添加新的监听器实现
- 灵活性:运行时决定调用哪个实现
- 设计模式 :符合观察者模式 和依赖倒置原则
类比:
arduino
就像打电话:
- 你拨的是"客服热线"(基类指针)
- 但接电话的是"小李"或"小王"(具体实现)
- 你不需要知道是谁接的,只需要知道能提供服务
🔬 验证示例代码
cpp
#include <iostream>
#include <memory>
class Animal {
public:
virtual void speak() = 0; // 纯虚函数
};
class Dog : public Animal {
public:
void speak() override { std::cout << "汪汪!\n"; }
};
class Cat : public Animal {
public:
void speak() override { std::cout << "喵喵!\n"; }
};
int main() {
std::shared_ptr<Animal> animal1 = std::make_shared<Dog>();
std::shared_ptr<Animal> animal2 = std::make_shared<Cat>();
animal1->speak(); // 输出: 汪汪! (调用 Dog::speak)
animal2->speak(); // 输出: 喵喵! (调用 Cat::speak)
// 虽然都是 Animal 指针,但调用的是各自的实现
return 0;
}
这就是为什么 mImageEditorProcessorListener->onGetImageList() 能"自动"找到正确实现的原因!