多态这个词听起来很高深,但其实就是让一段代码在不同场景下能表现出“多种形态”。多态的核心思想是“写一次代码,用在多种情况下”。C++ 里,多态分成两种: 动态多态 和 静态多态 。动态多态靠的是虚函数,而静态多态一般用 CRTP(Curiously Recurring Template Pattern)这种模板技巧来实现。我们今天就来聊聊这两种多态的原理、用法和各自的优缺点。
1
动态多态:虚函数的魔法
动态多态是 C++ 的经典特性。它通过虚函数实现,让子类可以重写父类的行为,从而实现“运行时的多态”。简单来说,就是程序运行时才决定用哪个函数。
虚函数的基本原理
虚函数的实现靠的是“虚函数表”(vtable)。每个含虚函数的类,背后都有一张虚函数表,记录了这个类的虚函数指针。对象调用虚函数时,会通过这张表找到对应的函数。
下面是个简单的例子:
运行结果 :
这里 animal->speak()
调用的是 Dog
的 speak()
,不是 Animal
的。原因是 speak
是虚函数,调用时通过虚函数表动态绑定到了 Dog
的实现。
动态多态的优缺点
优点 :
灵活,支持运行时动态行为变化。 易于扩展,新增子类无需修改父类代码。
缺点 :
多了一层间接调用,性能比直接调用稍差。 占用额外的内存(虚函数表)。
温馨提示 :如果一个类没有虚函数,它的对象通常是没有虚函数表的。不要随便加 virtual
,否则会让对象变胖。
2
静态多态:CRTP 的妙用
静态多态是通过模板实现的,编译时就能确定具体的行为,避免了动态多态的运行时开销。CRTP 是实现静态多态的常用技巧,名字听起来很吓人,其实原理很简单——“子类把自己作为模板参数传给父类”。
CRTP 的基本原理
直接上代码,看了就懂:
运行结果 :
这里的 Animal<Dog>
是一个模板类,Dog
继承了它,并通过 static_cast
把自己传递给了基类。speak
的调用在编译时就绑定到了具体实现。
静态多态的优缺点
优点 :
没有虚函数表,性能更高。 编译时确定行为,代码更可控。
缺点 :
写起来比较反直觉(这玩意儿看着就怪)。 子类和父类耦合度高,灵活性不如动态多态。
温馨提示 :CRTP 的模板代码会在编译时展开,可能会导致编译时间变长。你要是发现代码编译慢得像蜗牛,看看是不是模板太多了。
3
实战对比:什么时候用动态多态,什么时候用静态多态?
动态多态的适用场景
- 对象多样性
:需要支持一大堆子类,比如动物、车辆、用户接口等。 - 运行时灵活性
:编译时不知道具体类型,必须靠运行时决定。
比如游戏开发中,所有的敌人都继承自一个 Enemy
基类。敌人类型多种多样,动态多态可以轻松应对。
静态多态的适用场景
- 性能敏感
:比如嵌入式系统或游戏引擎的核心部分,时间和空间都很宝贵。 - 类型固定
:子类类型在编译时就能确定,不需要运行时灵活性。
比如数学库中,向量、矩阵等类型的运算,使用 CRTP 可以让性能最大化。
4
动态多态 vs 静态多态
绑定时间 | ||
性能 | ||
灵活性 | ||
扩展性 | ||
复杂度 |
这两种多态各有千秋,没有谁好谁坏。动态多态适合“灵活性优先”的场景,而静态多态适合“性能优先”的场景。一个是“跑得快”,一个是“变得快”。你可以根据实际需求,选择最合适的实现方式——这才是 C++ 的精髓。