A a;
a.foo(); // A::foo()被调用
}
1.1 多态
在了解了虚函数的意思之后,再考虑什么是多态就很容易了。仍然针对上面的类层次,
但是使用的方法变的复杂了一些:
void bar(A * a)
{
a->foo(); // 被调用的是 A::foo() 还是 B::foo()?
}
因为 foo()是个虚函数,所以在 bar 这个函数中,只根据这段代码,无从确定这里被调用
的是 A::foo()还是 B::foo(),但是可以肯定的说:如果 a 指向的是 A 类的实例,则 A::foo()
被调用,如果 a 指向的是 B 类的实例,则 B::foo()被调用。
“
”
这种同一代码可以产生不同效果的特点,被称为 多态 。
1.2 多态有什么用?
多态这么神奇,但是能用来做什么呢?这个命题我难以用一两句话概括,一般的 C++
教程(或者其它面向对象语言的教程)都用一个画图的例子来展示多态的用途,我就不
再重复这个例子了,如果你不知道这个例子,随便找本书应该都有介绍。我试图从一个抽
象的角度描述一下,回头再结合那个画图的例子,也许你就更容易理解。
在面向对象的编程中,首先会针对数据进行抽象(确定基类)和继承(确定派生类),
构成类层次。这个类层次的使用者在使用它们的时候,如果仍然在需要基类的时候写针对
基类的代码,在需要派生类的时候写针对派生类的代码,就等于类层次完全暴露在使用
“
”
者面前。如果这个类层次有任何的改变(增加了新类),都需要使用者 知道 (针对新类
写代码)。这样就增加了类层次与其使用者之间的耦合,有人把这种情况列为程序中的
“bad smell”之一。
多态可以使程序员脱离这种窘境。再回头看看 1.1 中的例子,bar()作为 A-B 这个类层次
的使用者,它并不知道这个类层次中有多少个类,每个类都叫什么,但是一样可以很好
的工作,当有一个 C 类从 A 类派生出来后,bar()
“
”
也不需要 知道 (修改)。这完全归功于
多态--编译器针对虚函数产生了可以在运行时刻确定被调用函数的代码。
1.3
“
”
如何 动态联编
编译器是如何针对虚函数产生可以再运行时刻确定被调用函数的代码呢?也就是说,
虚函数实际上是如何被编译器处理的呢?Lippman 在深度探索 C++对象模型[1]中的不同
“
”
章节讲到了几种方式,这里把 标准的 方式简单介绍一下。
“
”
“
我所说的 标准 方式,也就是所谓的 VTABLE”机制。编译器发现一个类中有被声明为
virtual 的函数,就会为其搞一个虚函数表,也就是 VTABLE。VTABLE 实际上是一个函数