说到面向对象特性之一“多态”,以我的水平已经说不出太多新意了。相信很多程序员代码K多了,做梦都在“多态中”运行着。常规的多态是C++语义内置支持的一种特性,通过虚函数可以实现这个特性,为了后面以示区别,我们姑且把这种多态称为“动态多态”或”运行期多态“,而本文总主要想讨论下“静态多态”,也可以叫“编译期多态”,同时一起来看下,静态多态会给我们带来哪些惊喜之处,拭目以待吧。
首先看个正常通过虚函数实现多态的常规例子,如下所示,很简单明了无需多言。
#include <iostream> #include <string> class BasicClassic { public: virtual void Print() = 0; }; class DerivedClassic1 : public BasicClassic { public: DerivedClassic1() {} virtual void Print() { std::cout << "DerivedClassic1 Print" << std::endl; } }; class DerivedClassic2 : public BasicClassic { public: DerivedClassic2() {} virtual void Print() { std::cout << "DerivedClassic2 Print" << std::endl; } };
通过虚函数,在运行时通过虚函数表指针,通过索引找到对应函数,然后进行调用,所以前面我们称之为动态多态或运行时多态。那静态多态又是怎么回事,如何实现呢?答案是模版。如果熟悉COM的同学应该见过框架中很多通过模版来实现多态的实现,看下如下的实现:
template<typename Derived> class Basic { public: inline void Print() { SelfCast()->Print(); } protected: inline Derived* SelfCast() { return static_cast <Derived*>(this); } }; class Derived1 : public Basic<Derived1> { public: Derived1() {} inline void Print() { std::cout << "Derived1 Print" << std::endl; } }; class Derived2 : public Basic<Derived2> { public : Derived2() {} inline void Print() { std::cout << "Derived2 Print" << std::endl; } static std::string Name() { return "Derived2 Class" ; } };
具体使用的代码:
Basic<Derived1>* der1 = new Derived1(); der1->Print(); Basic<Derived2>* der2 = new Derived2(); der2->Print();
输出结果:
Derived1 Print
Derived2 Print
这里实现的关键是SelfCast函数,通过static_cast把当前对象强制转换为具体指定的子类对象,这里是Derived1。其实实现的原理很简单,不难理解,我们需要重点讨论的是,这么样实现的多态跟常规虚函数的做法到底有什么不同,有什么优势?
大家应该都知道,虚函数的使用会带来额外的开销,具有虚函数的class类型都需要一张虚函数表,而每多一个虚函数,对应类型的对象的大小就会增加4bytes(32位机器下),夸张的试想一下如果有10个父类,每个父类都有100个虚函数的情况下,每个对象会增加多少?
4x10x100=4000bytes!
除了空间上的开销,每个虚函数的调用在时间上都会比普通函数多一次整形加法和一次指针间接引用,也就是时间上的开销。
这种开销虽然在绝大多数的应用中都是可以忽略不计的,但是总会存在一些对性能与开销无比在意的关键代码。根据28法则,应用中80%的时间都是在运行其中20%的代码,那么有时候对这20%代码的优化也许会带来显著的改善。
回到正题,我们把传统的实现方式称为动态多态,而模板方式的实现则是静态多态,归纳下他们的区别:
- 动态多态的多态性是在运行期决定的,而静态多态是在编译期就决定的
- 动态多态的实现需要更多空间上的开销,每个对象会因为一个虚函数而增加4bytes,静态多态则没有这个问题
- 动态多态的实现需要更多的时间开销,虚函数的调用在时间上都会比普通函数多一次整形加法和一次指针间接引用,静态多态中的调用则跟普通函数的调用开销相同
- 动态多态(虚函数)是C++编译器内置支持的一种实现方式,而静态多态则会额外带来一些使用的复杂性
- 动态多态中虚函数不能通过内联来优化执行效率,而静态多态中则可以通过内联来进一步优化函数执行效率
综上所述,在实际使用中,到底选择哪种实现方式,要因需而异,如果没有特别的性能需求时,完全没有必要为了写的更酷而去使用模版的方式来实现,反而得不偿失,但如果针对特别需求或关键性能的代码,则可以考虑这种优化。
另外再看一种使用方式,模版还可以实现static函数的类似多态特性,如下所示:
template <typename Derived> class Basic { public : Basic() { } inline void Print() { std::cout << Basic<Derived>::Name() << std::endl; SelfCast()->Print(); } static std::string Name() { return Derived::Name(); } protected : inline Derived* SelfCast() { return static_cast <Derived*>( this); } }; class Derived1 : public Basic<Derived1> { public : Derived1() {} inline void Print() { std::cout << "Derived1 Print" << std::endl; } static std::string Name() { return "Derived1 Class" ; } };
也就是说针对某些希望定义为static的函数,当你希望能在基类抽象方法中根据当前对象具体类型来使用子类相应static函数时,如上方法可以达成你的目的,这未尝不是一种不错的实现方式。
ok,静态多态就说到这,延伸还有哪些特定的应用,欢迎大家一起讨论。