简介
让我们从一个简单的Demo开始。
1 | Type1 x; |
要体现多态,f()
必须能够对至少两种不同的类型(e.g. int
and double
),查找并执行不同的代码实现。
简单来说,多态为不同类型的对象提供了一个同一个接口,它是面向对象编程领域的一个常见概念。此外,封装可以使得代码模块化,继承可以扩展已存在的代码,它们的目的都是为了代码重用。
多态的目的则是为了“接口重用”。也即,不论传递过来的究竟是类的哪个对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
C++的多态机制
根据绑定时间和实现方式的不同,C++中的多态可以分为:
形式 | 决议 |
---|---|
函数重载 | 编译期 |
操作符重载 | 编译期 |
模板 | 编译期 |
虚函数 | 运行时 |
编译期多态包括了重载和模板,对同一个接口,C++允许定义不同的参数列表来实现不同的行为;而运行时多态性是通过类的继承来实现的,通过重载父类虚函数,父类和子类以同一个接口实现不同的行为。
根据函数地址绑定的时间不同,多态也可以分为静态多态和动态多态。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,那么就是静态多态。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于动态多态。
一些实例
下面给出一些巧妙结合了多态和模板的例子。
1 |
|
通过继承+模板的方式,FunctionWrapper
擦除了可调用对象的类型,实现了一种统一调用的方式,这样的Callable广泛地用于回调、线程池等场景。
奇异递归模板模式(curiously recurring template pattern,CRTP)是C++模板编程时的一种惯用法(idiom):把派生类作为基类的模板参数。它也被称作F-bound polymorphism,相关介绍可以参考 wiki 。
1. CRTP的特点
- 继承自模板类;
- 使用派生类作为模板参数特化基类;
2. CRTP基本范式
CRTP如下的代码样式:
1 | template <typename T> |
这样做的目的是在基类中使用派生类,从基类的角度来看,派生类其实也是基类,通过向下转换downcast
,因此,基类可以通过static_cast把其转换到派生类,从而使用派生类的成员,形式如下:
1 | template <typename T> |
3. 一个简单例子
1 |
|
4. 常见使用
- 数学库Eigen、点云库PCL等三方库中广泛地使用了这一技巧来精简代码;
- std::enable_shared_from_this,在回调技术中至关重要的一个类,在后面的文章中会重点介绍(
现在不想写)。
总结
下面是一些简单的总结
静态多态
优点:
- 由于静多态是在编译期完成的,因此效率较高,编译器也可以进行优化;
- 有很强的适配性和松耦合性,比如可以通过偏特化、全特化来处理特殊类型;
- 模板编程为C++带来了泛型设计的概念,这在STL库中得到淋漓尽致的体现。
缺点:
- 由于是模板来实现静态多态,因此模板的不足也就是静多态的劣势,比如调试困难、编译耗时、代码膨胀、编译器支持的兼容性;
- 不能够处理异质对象集合;
动态多态
优点:
- OO设计,对是客观世界的直觉认识;
- 实现与接口分离,可复用;
- 处理同一继承体系下异质对象集合的强大威力
缺点:
- 运行期绑定,导致一定程度的运行时开销;
- 编译器无法对虚函数进行优化;
- 笨重的类继承体系,对接口的修改影响整个类层次;
多态并不是银弹,因地制宜、见招拆招才是解决问题最通用的办法。