多态性可按照发生的时间段分为静多态(Static Polymorphism)和动多态(Dynamic Polymorphism)。
其中静多态就是绑定发生在编译期(compile-time),此种绑定称为静态绑定static-binding);而动多态就是绑定发生在运行期(run-time),此种绑定称为动态绑定(dynamic-binding)。
静多态可以通过模板和函数重载来实现,具体可以分为:
1)非参数化多态(Ad-hoc polymorphism,特别多态):
a)函数重载(Function Overloading)
b)运算符重载(Operator Overloading)
2)参数化多态(Parametric polymorphism)
c)模板(Template)
其实非参数化多态和参数化多态并不冲突,而且相辅相成,它们混合使用能够带来更大的灵活性,函数模板重载就是很好的例子:
1 template<typename T> 2 T max(const T& lhs, const T& rhs) 3 { 4 return lhs > rhs ? lhs : rhs; 5 } 6 template<typename T> 7 T max(const T& fst, const T& sec, const T& thd) 8 { 9 return max(max(fst, sec), thd); 10 }
动多态是通过继承、虚函数(virtual)、指针来实现。(三者结合起来实现)
动多态在C++中是通过虚函数实现的,即在基类中存在一些接口(一般为纯虚函数),子类必须重载这些接口。这样通过使用基类的指针或者引用指向子类的对象,就可以实现调用子类对应的函数的功能。动多态的函数调用机制是执行期才能进行确定,所以它是动态的。
有人也将Ad-hoc polymorphism剥离出来单独作为一类,实际上将多态分为:
1)通用多态(universal polymorphism)
a) 参数多态(parametric polymorphism)
b) 包含多态(inclusion polymorphism)或称子类型多态(subtyping polymorphism)
从实现机制上看,二者的不同之处在于何时将一个变量与其实际类型所定义的行为挂钩。前者在编译期,属于早绑定 (early binding)或静态绑定(static binding);后者在运行期,属于迟绑定 (late binding)或动态绑定(dynamic binding)。从应用形式上看,前者是发散式的,让相同的实现代码应用于不同的场合;后者是收敛式的,让不同的实现代码应用于相同的场合。从思维方式上看,前者是泛型式编程风格,看重的是算法的普适性;后者是对象式编程风格,看重的是接口与实现的分离度。尽管二者从范式到语法、语义都大相径庭,但都是为着同一个目的:在保证必要的类型安全的前提下,突破编译期间过于严苛的类型限制。
2)特别多态(ad-hoc polymorphism)
c) 强制多态(coercion polymorphism),即一种类型的变量在作为参数传递时隐式转换成另一种类型,比如一个整型变量可以匹配浮点型变量的函数参数。
d) 重载多态(overloading polymorphism),它允许不同的函数或方法拥有相同的名字。
这个分类方法可以参考wiki:
2 Forms of polymorphism
- 2.1 Ad-hoc polymorphism
- 2.2 Parametric polymorphism
- 2.3 Subtype polymorphism (or inclusion polymorphism)
静多态是以牺牲灵活性而获得运行速度的一种做法;而动多态则恰恰相反,它是以牺牲运行速度而获取灵活性的做法。动多态的实现要通过虚函数,会产生虚表和虚指针,占用较多的空间,动多态需要在运行期进行绑定,所花费的时间较静多态多。即静多态在空间和时间上都比动多态表现的好,因此在其他的条件相同的情况下,应该更多的使用静多态。但是静多态也有一些缺点,如它不能够处理异类的集合,也没有动多态灵活。并且现在大部分编译器不支持模板的分离编译,因此要将声明和实现写在一个文件中,这样会暴露更多的细节。
到底用动多态还是静多态,应该根据情况综合考虑决定。我们也可以结合动多态和静多态来获得一个很好的效率和灵活性。
使用静多态来实现动多态
这是一种在模板元编程(Template Metaprogramming)中常见的标准编程技巧。在C++中,可以借助模板来实现面向对象语言所支持动多态相似的功能特性。下面是C++本身所支持动多态形式:
1 #include <iostream> 2 class Base { 3 public: 4 virtual void method() = 0; 5 virtual ~Base() { } 6 }; 7 class Derived : public Base { 8 public: 9 virtual void method(){ 10 std::cout << "Derived" << std::endl; 11 } 12 }; 13 class Derived2 : public Base { 14 public: 15 virtual void method(){ 16 std::cout << "Derived2" << std::endl; 17 } 18 }; 19 int main(){ 20 Base *pBase = new Derived; 21 pBase->method(); // 输出:"Derived" 22 delete pBase; 23 Base *pBase2 = new Derived2; 24 pBase2->method(); // 输出:"Derived2" 25 delete pBase2; 26 27 return 0; 28 }
下面是使用CRTP(Curiously Recurring Template Pattern)来实现多与上面对应功能的静多态代码:
1 #include <iostream> 2 template <class Derived> 3 class Base { 4 public: 5 void method(){ 6 // ... 7 static_cast<Derived*>(this)->implementation(); 8 // ... 9 } 10 }; 11 class Derived : public Base<Derived> { 12 public: 13 void implementation(){ 14 std::cout << "Derived" << std::endl; 15 } 16 }; 17 class Derived2 : public Base<Derived2> { 18 public: 19 void implementation(){ 20 std::cout << "Derived2" << std::endl; 21 } 22 }; 23 int main(){ 24 Base<Derived> *pBase = new Base<Derived>(); 25 pBase->method(); // 输出:"Derived" 26 delete pBase; 27 Base<Derived2> *pBase2 = new Base<Derived2>(); 28 pBase2->method(); // 输出:"Derived2" 29 delete pBase2; 30 31 return 0; 32 }
虽然使用这种方式实现的多态和面向对象中的多态从功能上说差不多相同,但是前者没有后者易用、易懂、和能力强大。虽然如此,CRTP作为一种模板设计模式还是很有用的,例如,Boost iterator library就是用了这种方法来实现。
其实在别的语言中也存在CRTP这种模式,如Java,Enum类被定义为Enum<T extends Enum<T>>,当然由于Java在模板方面的不足,作为Java语言的使用者,你是没法自己体验这样的用法(Java虽然支持模板特性,但是用户不能自己定义模板,只能使用库里边的模板类)。
参考:C++多态性(静多态和动多态), C++的动多态和静多态,冒号课堂§10.1:多态类型,Polymorphism (computer science)