时至今日的C++已经发展成为一种多次语言所组成的语言集合,而其中泛型编程与基于它的STL是C++发展历程中最为精彩的那一部分。在C++的面向对象编程中,多态是OO三大特性之一,我们称为运行期多态,也称它为动态多态;但在泛型编程中,多态是基于模板的具现化与函数的重载解析,由于这种多态发生于编译期,所以称它为编译期多态或静态多态。
运行期多态
运行期多态的设计来源于类继承体系的设计上。面对有相关功能的对象集合时,我们总致力于抽象出它们共有的功能集合,因此在基类中将这些功能声明为虚接口(虚函数),我们用子类继承基类去重写这些虚接口,因而实现了子类特有的具体功能。

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的是哪个具体类型的接口。而不同的推断结果所调用的函数亦不同,这就是编译器多态。
运行期多态与编译期多态优缺点分析
运行期多态优点
1、OO设计中的重要特性,对于客观世界的直觉认识。
2、能够处理相同继承体系下的异质类集合。
运行期多态缺点
1、运行期间才会进行虚函数绑定,这将提高程序运行开销。
2、庞大复杂的类继承层次,对于接口的修改容易影响类继承层次。
3、由于虚函数是在运行期间才确定,所以导致编译器无法对虚函数进行优化。
编译期多态优点
1、泛型编程的概念,使C++拥有泛型编程与STL这样的强大能力。
2、在编译期完成多态,远远提高了运行期效率。
3、强大的适配性与松耦合性,对特殊类型可由模板偏特化、全特化来处理。
编译期多态缺点
1、程序可读性降低,给代码调试带来困难。
2、不能实现模板的分离编译,当工程很大时,编译时间也越大。
3、不能处理异质对象集合。
关于显式接口与隐式接口
显式接口是指类继承层次中定义的接口或者是某个具体类提供的接口,我们能在源代码中找到这个接口。显式接口将以函数签名为中心,例如:
void AnimalShot(Animal & anim)
{
anim.shout();
}
我们将shout当做一个显式接口。在运行期多态中的接口皆是显式接口。而对于模板参数而言,接口是隐式的,奠基于有效表达式。例如:
template <typename T>
void AnimalShot(T & anim)
{
anim.shout();
}
对于anim而言,支持哪一种接口,要由模板参数来决定,在上面这个例子中,T必须支持shout()操作,那么shout就是T的一个隐式接口。
欢迎关注软件特攻队!