1. 先说结论:
动态多态是指:把基类的 virtual 虚函数作为统一接口,调用多个派生类的同名函数。
使用多态的 4 个主要步骤:
- 基类中定义 virtual 虚函数,作为接口函数。
- 派生类中重写 override 基类的接口函数。
- 创建基类的引用(或指针),用于接收派生类对象,并调用基类中的接口函数。
- 把派生类对象传递给基类的引用,即可调用派生类的同名函数,实现多态。
2. 示例代码
可以创建一个示例代码,使用多态时直接套用上去即可。
// 该程序作为一个模版,演示如何使用动态多态。
#include <iostream>
#include <memory>
// 1. 基类中定义 virtual 虚函数,作为接口函数。
class AnimalBase {
public:
// 1.1 定义虚函数,作为统一的接口函数。
virtual void speak_interface() const {
std::cout << "In speak_interface" << std::endl;
};
AnimalBase() {
std::cout << "AnimalBase constructor" << std::endl;
}
virtual ~AnimalBase() { // 1.2 析构函数使用 virtual ,进行主动防御型编程。
std::cout << "AnimalBase de-constructor" << std::endl;
}
};
// 2. 派生类中重写 override 基类的接口函数。
// 因为大黄 class 已经很具体,不应再被继承创建子类,所以下面用 final 。
class DaHuang final : public AnimalBase {
public:
void speak_interface() const override { // 2.1 派生类 override 基类的接口函数。
std::cout << *age_ << " 岁的大黄汪汪叫!" << std::endl;
}
DaHuang(int age) : age_{std::make_unique<int>(age)}{
std::cout << "构造大黄。" << std::endl;
}
~DaHuang() override {
std::cout << "析构大黄。age_ 的堆区数据已清理。" << std::endl;
}
private: // 2.2 派生类如果用到指针,则使用智能指针。
std::unique_ptr<int> age_ = std::make_unique<int>(8);
};
class CatDerived : public AnimalBase {
public: // 2.1 派生类 override 基类的接口函数。
void speak_interface() const override {
std::cout << "Cat: meow!" << std::endl;
}
CatDerived() {
std::cout << "Cat constructor" << std::endl;
}
~CatDerived() override {
std::cout << "Cat de-constructor" << std::endl;
}
};
// 3. 创建基类的引用(或指针),用于接收派生类对象,并调用基类中的接口函数。
void call_interface(AnimalBase& animal) {
animal.speak_interface();
}
int main() {
int dahuang_age = 888;
{
std::cout << "\n1. 使用引用,顺利清理 age_ 内存数据。" << std::endl;
// 4. 把派生类对象传递给基类的引用,即可调用派生类的同名函数,实现多态。
DaHuang dahuang{dahuang_age};
call_interface(dahuang);
}
{
std::cout << "\n2. 调用多个子类中的同名函数。" << std::endl;
CatDerived cat;
call_interface(cat);
}
{
std::cout << "\n3. 即便传入基类指针,同样可以清理堆区的 age_ 数据。" << std::endl;
std::unique_ptr<AnimalBase> dahuang_2{std::make_unique<DaHuang>(100)};
call_interface(*dahuang_2);
}
return 0;
}
运行结果如下图:
3. 几个说明
- 动态多态的大致原理是:派生类的对象中,会有一个虚表指针,该指针指向了派生类的虚函数表。通过这个虚函数表,就可以调用派生类 override 之后的虚函数。
- 使用 std::unique_ptr 智能指针,避免手动 delete 堆区数据。
- 关于 virtual 关键字的用法,可参见我的另一篇文章:
《C++ 中 virtual 的作用》 https://blog.csdn.net/drin201312/article/details/147686161
4. 和 Python 的对比
C++ 的动态多态比较复杂,是因为 C++ 对数据类型有强制要求,而基类和派生类都有各自的类型,因此实现多态较复杂。
Python 的多态则非常简单,因为 Python 不对类型做强制要求,只使用 duck typing 机制。
具体来说, Python 中只需要 2 个对象 foo 和 bar 中有同名的方法,就可以实现多态。而且 foo 和 bar 可以属于两个完全无关的 class 。
—————————— 本文结束 ——————————