偷梁换柱——揭开多态的面纱
——从C++到.NET
声明:本文的前半部分纯粹是为初学者写的,如果你对C++对象模型稍有了解,就不用看了。后半部分才把.NET的对象模型拉进来做了个简单的比较——总之,如果你不是初学者就不必看了,我也实在不想浪费你的时间:-)
刘未鹏(pongba) /文
多态是面向对象理论中的重要概念之一,从而也成为现代程序设计语言的一个主要特性,从应用角度来说,多态是构建高灵活性低耦合度的现代应用程序架构所不可忽缺的能力。从概念的角度来说,多态使得程序员可以不必关心某个对象的具体类型,就可以使用这个对象的“某一部分”功能。这个“某一部分”功能可以用基类来呈现,也可以用接口来呈现。后者显得更为重要——接口是使程序具有可扩展性的重要特性,而接口的实现依赖于语言对多态的实现,或者干脆就象征着语言对多态的实现。
本文并不大算赘述多态的应用,因为其应用实在俯拾皆是,其概念理论也早已完善。这里,我们打算从实现的角度来看一看一门语言在其多态特性的背后做了些什么——知其所以然,使用时方能游刃有余。
或许你在学习一门语言的时候,曾经对多态的特性很迷惑,虽然教科书上所讲的非常简单,也非常明了——正如它的原本理念一样,但是你也想知道语言(编译器)在背后都干了些什么,为什么一个派生类对象就可以被当作其基类对象来使用?用指向派生类对象的基类指针调用虚函数时凭什么能够精确的到达正确的函数?类的内部是如何布局的?
我们这样考虑:假设语言不支持多态,而我们又必须实现多态,我们可以怎么做?
多态的雏形:
class B
{
public:
int flag; //为表示简洁,0代表基类,1代表派生类
void f(){cout<<”in B::f()”;} //非虚函数
};
class D:public B
{
public:
void f(){cout<<”in D::f()”;} //非虚函数
};
void call_virtual(B* pb)
{
if(pb->flag==0) //如果是基类,则直接调用f
pb->f(); //调用的是基类的f
else //如果是派生类,则强制转化为派生类指针再调用f
(D*)pb->f(); //调用的是派生类的f
}
这样,可以正好符合“根据具体的对象类型调用相应的函数”的理念。但是这个原始方案有一些缺点:;例如,分发“虚函数”的代码要自己书写,不够优雅,不具有可扩展性(当继承体系扩大时,这堆代码将变得臃肿无比),不具有封闭性(如果加入了一个新的派生类,则“虚函数”调用的代码必须作改动,然而如果恰巧这个调用是无法改动的(例如,库函数),则意味着