今日的C++再也不是个单纯的“带类的C”语言,它已经发展成为一个多种次语言所组成的语言集合,其中泛型编程与基于它的STL是C++发展中最为出彩的那部分。在面向对象C++编程中,多态是OO三大特性之一,这种多态称为运行期多态,也称为动态多态;在泛型编程中,多态基于template(模板)的具现化与函数的重载解析,这种多态在编译期进行,所以称为编译期多态或静态多态。在本文中,咱们将了解:html
运行期多态的设计思想要归结到类继承体系的设计上去。对于有相关功能的对象集合,咱们总但愿可以抽象出它们共有的功能集合,在基类中将这些功能声明为虚接口(虚函数),而后由子类继承基类去重写这些虚接口,以实现子类特有的具体功能。典型地咱们会举下面这个例子:
编程
class Animal { public : virtual void shout() = 0; }; class Dog :public Animal { public: virtual void shout(){ cout << "汪汪!"<<endl; } }; class Cat :public Animal { public: virtual void shout(){ cout << "喵喵~"<<endl; } }; class Bird : public Animal { public: virtual void shout(){ cout << "叽喳!"<<endl; } }; int main() { Animal * anim1 = new Dog; Animal * anim2 = new Cat; Animal * anim3 = new Bird; //藉由指针(或引用)调用的接口,在运行期肯定指针(或引用)所指对象的真正类型,调用该类型对应的接口 anim1->shout(); anim2->shout(); anim3->shout(); //delete 对象 ... return 0; }
运行期多态的实现依赖于虚函数机制。当某个类声明了虚函数时,编译器将为该类对象安插一个虚函数表指针,并为该类设置一张惟一的虚函数表,虚函数表中存放的是该类虚函数地址。运行期间经过虚函数表指针与虚函数表去肯定该类虚函数的真正实现。函数
运行期多态的优点还在于它使处理异质对象集合称为可能:优化
//咱们有个动物园,里面有一堆动物 int main() { vector<Animal*>anims; Animal * anim1 = new Dog; Animal * anim2 = new Cat; Animal * anim3 = new Bird; Animal * anim4 = new Dog; Animal * anim5 = new Cat; Animal * anim6 = new Bird; //处理异质类集合 anims.push_back(anim1); anims.push_back(anim2); anims.push_back(anim3); anims.push_back(anim4); anims.push_back(anim5); anims.push_back(anim6); for (auto & i : anims) { i->shout(); } //delete对象 //... return 0; }
总结:运行期多态经过虚函数发生于运行期设计
对模板参数而言,多态是经过模板具现化和函数重载解析实现的。以不一样的模板参数具现化致使调用不一样的函数,这就是所谓的编译期多态。
相比较于运行期多态,实现编译期多态的类之间并不须要成为一个继承体系,它们之间能够没有什么关系,但约束是它们都有相同的隐式接口。咱们将上面的例子改写为:指针
class Animal { public : void shout() { cout << "发出动物的叫声" << endl; }; }; class Dog { public: void shout(){ cout << "汪汪!"<<endl; } }; class Cat { public: void shout(){ cout << "喵喵~"<<endl; } }; class Bird { public: void shout(){ cout << "叽喳!"<<endl; } }; template <typename T> void animalShout(T & t) { t.shout(); } int main() { Animal anim; Dog dog; Cat cat; Bird bird; animalShout(anim); animalShout(dog); animalShout(cat); animalShout(bird); getchar(); }
在编译以前,函数模板中t.shout()调用的是哪一个接口并不肯定。在编译期间,编译器推断出模板参数,所以肯定调用的shout是哪一个具体类型的接口。不一样的推断结果调用不一样的函数,这就是编译器多态。这相似于重载函数在编译器进行推导,以肯定哪个函数被调用。调试
虚表指针增大了对象体积,类也多了一张虚函数表,固然,这是理所应当值得付出的资源消耗,列为缺点有点勉强。code
具备很强的适配性与松耦合性,对于特殊类型可由模板偏特化、全特化来处理。htm
所谓的显式接口是指类继承层次中定义的接口或是某个具体类提供的接口,总而言之,咱们可以在源代码中找到这个接口.显式接口以函数签名为中心,例如对象
void AnimalShot(Animal & anim) { anim.shout(); }
咱们称shout为一个显式接口。在运行期多态中的接口皆为显式接口。
而对模板参数而言,接口是隐式的,奠定于有效表达式。例如:
template <typename T> void AnimalShot(T & anim) { anim.shout(); }
对于anim来讲,必须支持哪种接口,要由模板参数执行于anim身上的操做来决定,在上面这个例子中,T必须支持shout()操做,那么shout就是T的一个隐式接口。