大学IT网 - 最懂大学生的IT学习网站! QQ资料交流群:367606806
当前位置:大学IT网 > C++技巧 > C++多态技术的实现和反思

C++多态技术的实现和反思(1)

关键词:c++多态  阅读(1069) 赞(12)

[摘要]本文是对C++多态技术的实现和反思的讲解,希望对您学习C++程序设计有所帮助。

面向对象技术最早呈现于1960年代的Simula 67零碎,并且在1970年代保罗阿托实验室开发的Smalltalk零碎中展开成熟。但是关于大局部顺序员来说,C++是第一个可用的面向对象顺序设计言语。因而,我们关于面向对象的很多概念和思想直接来自于C++。但是,C++在完成面向对象中关键的多态性时,选择了与Smalltalk完全不同的方案。其后果是,虽然在外表上两者都完成了类似的多态性,但是在实际中却有着庞大的区别。详细的说,C++的多态性完成愈加高效,但是并不适用于一切场所。很多阅历缺乏的C++开发者不明白这个道理,在不适合的场所强行运用C++的多态性机制,落入削足适履的圈套而不能自拔。本文将详细讨论C++多态性技术的局限性及处置的方法。

  两种不同虚办法调用完成技术

  C++的多态性是C++完成面向对象技术的根底。详细的说,经过一个指向基类的指针调用虚成员函数的时分,运转时零碎将可以依据指针所指向的理论对象调用恰当的成员函数完成。如下所示:

class Base {
  public:
   virtual void vmf() { ... }
  };
  
  class Derived : public Base {
  public:
   virtual void vmf() { ... }
  };
  
  Base* p = new Base();
  p->vmf(); // 这里调用Base::vmf
  p = new Derived();
  p->vmf(); // 这里调用
// Derived::vmf
  ...

  请留意代码中突出正文的两行,虽然其外表语法完全相反,但是却区分调用了不同的函数完成。所谓的“多态”即就此而言。这些知识是每一个C++开发者都熟知的。

  如今我们假定本人是言语的完成者,我们该当如何来完成这种多态性?稍加思索,我们不难失掉一个根本的思绪。多态性的完成要求我们添加一个直接层,在这个直接层中阻拦关于办法的调用,然后依据指针所指向的理论对象调用相应的办法完成。在这个进程中我们人为
添加的这个直接层十分重要,它需求完成以下几项义务:

  1. 获知办法调用的全部信息,包括被调用的是哪个办法,传入的理论参数有哪些。

  2. 获知调用发作时指针(援用)所指向的理论对象。

  3. 依据第1、2步取得的信息,找到适合的办法完成代码,执行调用。
  
  这里的关键在于如何在第3 步中找到适合的办法完成代码。由于多态性是就对象而言的,因而我们在设计时要把适合的办法完成代码与对象绑定到一同。也就是说,必需在对象级别完成一个查找表构造,依据1、2步取得的对象和办法信息,在这个查找表中找到理论的办法代码地址,并加以调用。如今效果变成了,我们该当依据什么信息中止办法查找。关于这个效果有两个不同的处置思绪,一个是依据称号中止查找,另一个是依据地位中止查找。粗看上去这两种思绪似乎没什么大的差异,但是在实际中,这两种不同的完成思绪招致了庞大的差异。上面我们详细地加以调查。

  在Smalltalk、Python、Ruby等静态面向对象言语中,理论办法的查找是依据办法称号中止的,其查找表构造如下:

  由于这种查找表依据办法的称号中止办法查找,因而在查找进程中触及字符串比拟,效率较差。但是这种查找表有一个突出的优点,就是无效空间运用率高。为了阐明这一点,我们假定一个基类Base中有100个办法可供派生类改写(因而一切Base对象所共享的办法查找表有100项),而它的一个派生类Derived仅仅只方案改写其中5个办法,那么Derived类对象的办法查找表只需求5项。当一个办法调用发作的时分,runtime依据被调用的办法称号在这个长度为5 的办法查找表中中止字符串查找,假定发现该办法在查找表中,则执行调用,否则将调用转寄(forward)给Base类执行。这是虚办法调用的规范行为。当派生类理论改写的办法数量很少的时分,可以将查找表布置成线性表,查找时顺序比拟,这种状况下无效空间运用率抵达100%。假定派生类理论改写的办法数量较多,那么可以采用散列表,假定采用合理的散列函数,异常可以在空间运用率很高(普通可接近75%).. 的状况下完成办法的疾速查找。该当留意到,由于编译器可以很容易地取得一切被改写办法的称号,因而可以执行规范的gperf算法取得最优的散列函数。

<理想上,我们还可以这样了解这种方案的优势,把表中每一项的“办法名”项视为“办法地址”项的描画信息,因而可以以为这种方案中的办法查找表携带自描画信息(或许称为元数据)。基于这种携带自描画信息的数据构造,可以完成丰厚多彩的扩展功用,比方在运转时
拔出新的办法,或许用户层次上的办法调用截获等。因而,我们可以说这一方案的适用面广,弱小灵敏,但在执行效率上并非最优。

  另一种虚办法查找方案则是C++ 开发者非常熟习的,基于相对地位的定位技术。其查找表构造十分复杂,仅仅是一个寄存了办法地址的指针数组。表中的每一项不具有自描画性,只需编译器在编译时晓得它们终究区分对应着哪一个办法,并且将关于办法的调用代码编译成一个紧凑的指针+偏移的调用的硬编码。这种查找表的最大特点就是高效率,基于这种查找表中止办法调用仅仅需求多做一次数组内的随机拜访操作。在一切我们所能想到的“添加一个直接层”的方案中,这种方案在效率上是最高的。但是运用这种方案有一个限定,就是要求一切同族多态对象具有完全一样的查找表。也就是说,你必需确保一切完成了某个接口的对象的虚办法查找表的第k 项都具有相反的语义。假定一个基类有100个可供改写的虚办法,那么它的虚办法查找表共有100项(理论上就是100个指向办法入口地址的指针)。而其一切派生类对象都必需有构造上完全相反的、长度至多为100项的虚办法查找表。如今假定我们开发的一个派生类中只改写了基类的5个办法,那么这个派生类对象所共享的虚办法表依然长达100项,只不过其中95项与其基类对象虚办法查找表中相应的项如出一辙,只需5项具有理论意义——正是这5项的存在才使派生类的存在有了意义。

  在这种状况下,该办法表的理论无效运用率只需不幸的5%。总的来说,这一方案执行效率最优,但是并不适用于一切的场所。

  当然,看上去上述两种虚办法调用完成技术效果完全一样,一切都被掩盖在编译器之下,与普通开发者毫有关系。但是,理想真的如此吗?我们在上面会看到,C++ 的这种查找表构造构成了C++运用开发中最险峻的技术圈套之一。

«上一页12下一页»


相关评论