您的位置:首页 > 编程语言 > C语言/C++

关于C++多态性的一些总结

2015-03-31 07:59 330 查看
在任何一门面向对象的编程语言中,多态性(polymorphism)都是非常重要的一个概念。在面向对象的三大元素中,封装使代码的模块化变得简单,继承则可以扩展已有的代码,而多态则是为了满足接口的重用。所谓的多态,通俗的来讲,其实就是让不同的对象在接受到相同的消息时能够做出不同的反应,好像有多个类型一般,这体现了对象的自恰性。打个比方,两军对阵,同样是鸣锣,可能一方军队在听到锣声时会采取撤退的行动;而另一方的军队听到锣声后,则可能会采取发动进攻的行动。关键在于双方军队各自都有对听到锣声这一信号而采取什么样的行动有所对应。多态也是一样。在满足继承关系的两个类中,只要在基类中定义了一个虚函数,且子类中对该虚函数提供了有效的覆盖版本,那么在通过指向子类的基类指针或引用子类的基类引用调用该函数时,会由调用对象的具体类型来确定调用哪个对象的函数,这种现象称之为多态(运行期多态)。在C++中,多态性的实现有两种形式.——动态的多态性(运行时的多态性)和静态的多态性(编译时的多态性),要么在运行时决定,要么在编译时决定。其中动态的多态性正是通过虚函数表来实现的。下面先让我们来看一段代码(运行环境:64位Ubuntu
14.04 LTS + gcc 4.8.2):

#include <iostream>
using namespace std;
class Shape{
public:
Shape(void){}
virtual void show(void){
cout << “Shape” << endl;
}
virtual void foo(void){
cout << “Shape::foo” << endl;
}
};
class Circle:public Shape{
public:
Circle(void){}
void show(void){
cout << “Circle” << endl;
}
};
class Rectangle:public Shape{
public:
Rectangle(void){}
void show(void){
cout << “Rectangle” << endl;
}
};
int main(void){
Circle c;
Rectangle r;
Shape* p = &c;
p->show();			//Cricle
p = &r;
p->show();			//Rectangle
Shape& pr = c;
pr.show();			//Circle
Shape& pt = r;
pt.show();			//Rectangle
return 0;
}



从这幅图中可以看出,在Shape对象中有一个虚表指针(_vptr_Shape),指向了Shape中的虚函数表,而虚函数表高地址部分内容中有两个函数指针,分别指向了Shape基类中的show函数和foo函数。整个多态的过程是这样的:在实例化Circle对象时会先实例化Shape子对象,这时虚表指针指向了Shape类中的虚函数表。当Shape子对象构建完成后会去构建Circle的独有部分,这个时候会将虚表指针的值改为Circle类的虚函数表的地址。由于Circle提供了对show函数的有效覆盖,而没有提供对foo函数的有效覆盖,所以在Circle对象的虚表指针(_vptr_Circle)所指向的虚函数表中,Circle::show函数代替了Shape::show函数,而foo函数仍然为Shape::foo函数。当我们使用一个指向子类的基类指针或引用子类的基类引用时,操作系统会先根据指针所指向的对象找到虚表指针,再根据虚表指针找到对应的类中的虚函数表,再来调用相应的函数,这就是通过虚函数表来实现运行时的多态的原理

要实现运行时多态必须满足以下几个条件:

1. 在基类中定义了虚函数且子类中提供了对基类虚函数的有效覆盖

2. 必须通过指向子类的基类指针或引用子类的基类引用来调用

3. 只有成员函数形式的运算符重载才能实现多态性,全局函数形式的运算符重载无法实现多态

4. 调用虚函数的指针也有可能是基类的this指针,同样满足多态的条件,但在构造函数和析构函数中除外

有效的覆盖(override)应满足的条件:

1. 必须是使用virtual声明的成员函数,不能是静态成员函数或全局函数

2. 覆盖版本中必须带有和基类虚函数中完全相同的函数签名,即函数名,形参表和常属性

3. 如果基类中的虚函数的返回类型是基本数据类型,那么子类中的覆盖版本必须返回相同的基本数据类型;如果基类中的虚函数返回的是类类型的指针或引用,那么允许子类中的覆盖版本返回其子类类型的指针或引用

4. 如果基类版本的虚函数带有异常说明,那么子类覆盖版本不能说明比基类版本的虚函数更多的异常说明(可以少不可以多)

5. 无论基类版本位于基类中的public,private,protected部分中,子类中的覆盖版本可以出现在子类中包括public,private,protected的任何部分

虽然运行时多态使用形式简单,但是它会给程序带来一定的开销,对性能的影响主要体现在以下几点:

1. 虚函数表本身会增加内存空间的开销

2. 与普通函数相比,虚函数的调用
要多出几个步骤,增加了运行时间的开销

3. 动态绑定会妨碍编译器来通过内联优化代码(最突出)

既然动态多态性(运行时多态)是通过虚函数表来实现,那么静态的多态性(编译时多态)则是通过什么来实现的呢?先来看一下下面的 代码(运行环境:64位Ubuntu 14.04 LTS + gcc 4.8.2):

#include <iostream>
using namespace std;
class Circle{
public:
void show(void){
cout << ”Circle” << endl;
}
};
class Rectangle{
public:
void show(void){
cout << ”Rectangle” << endl;
}
};
template<typename Shape>
void drawAny(Shape& shape){
shape.show();
}
int main(void){
Circle c;
Rectangle r;
drawAny(r);
drawAny(c);
return 0;
}
运行结果如下:

Rectangle

Circle

从上面代码运行结果可以看出,我们的drawAny实现了多态。在面对同样的函数调用时,我们通过传递不同的参数来实现不同的函数调用。这种多态性称之为编译期多态性,这是因为在编译的过程中,编译器在看到函数模板定义的时候还不知道其类型,这时编译期会先忽略类型,做一些跟类型无关的语法检查,然后生成一个内部表示。当编译器看到对该函数模板的调用时,便会结合具体的参数类型生成相应函数的二进制代码。也就是说,在上面的那个例子中,我们利用了函数模板的隐式推断,将我们要调用的类类型对象通过引用的方式传递给了函数模板,编译器通过对参数类型的确认,确定了被调用的函数所属的对象,进而确定了调用的是哪个具体函数,实现了多态。这种方法相对于通过使用虚函数表实现的运行时多态,实现较为复杂,不如虚函数简单易用,但是它的运行效率要比通过使用虚函数表实现的多态方式要高。

除此之外,我们也可以使用函数重载来实现多态,这一样也是属于编译期多态。来看一段代码(运行环境:64位Ubuntu 14.04 LTS + gcc 4.8.2):

#include <iostream>
using namespace std;
class Circle{
public:
void show(void){
cout << ”Circle” << endl;
}
};
class Rectangle{
public:
void show(void){
cout << ”Rectangle” << endl;
}
};
void drawAny(Circle & circle){
circle.show();
}
void drawAny(Rectangle & rect){
rect.show();
}

int main(void){
Circle c;
Rectangle r;
drawAny(r);
drawAny(c);
return 0;
}
运行结果如下:

Rectangle

Circle

由上面代码可以看出,使用函数重载同样也可以实现多态性。面对同样的函数调用,都能得到不同的正确结果。这种方法实现起来非常简单,但是过程繁复,而且不具备可扩展性。程序中的需要实现多态特性的类个数少的话还行,但如果多呢?可复用性和可扩展性都太差,一般都不建议使用。在这里简单的提一下重载(overload)、覆盖(override)和隐藏()的区别:在同一作用域中,函数名相同,参数表不同,则构成重载关系;在具有继承关系的父子类中,带有virtual关键字,且函数签名完全相同,则构成覆盖关系;若不在同一作用域中,函数名相同,则构成隐藏关系

总结:

1. 使用虚函数实现多态时,由于编译器内置的支持,使用起来较为简单,而使用模板实现多态较为复杂,使用函数重载实现多态则非常简单,但是缺乏可复用性和可扩展性。

2. 使用运行时多态会增加程序的开销,而编译期多态对程序的开销几乎没有影响。

3. 使用模板来实现的多态无法通过基类对象指针数组来实现对多个不同子类对象的多态操作,而使用虚函数可以达到这一目标
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: