Chinaunix首页 | 论坛 | 博客
  • 博客访问: 544697
  • 博文数量: 104
  • 博客积分: 2089
  • 博客等级: 大尉
  • 技术积分: 1691
  • 用 户 组: 普通用户
  • 注册时间: 2010-06-29 08:48
文章分类

全部博文(104)

文章存档

2015年(1)

2013年(13)

2012年(31)

2011年(59)

分类: C/C++

2011-12-16 15:19:56

pure irtual function  -- 纯虚函数

先看例子
  1. #include <iostream>
  2. using namespace std;

  3. class Polygon {
  4.         protected:
  5.                 int width, height;
  6.         public:
  7.                 void set_values (int a, int b)
  8.                 { width=a; height=b; }
  9.                 virtual int area() = 0 ;//{return 0;}
  10.                 // _vptr.Polygon show difrent value with
  11.                 // the gdb command: p *this
  12.                 // this is a access into virtual table
  13. };

  14. class Triangle: public Polygon {
  15.         public:
  16.                 int area ()
  17.                 { return (width * height / 2); }
  18. };

  19. class Rectangle: public Polygon {
  20.         public:
  21.                 int area ()
  22.                 { return (width * height); }
  23. };

  24. class Pentagon: public Polygon {
  25.         public:
  26.         /*
  27.                 int area ()
  28.                 { return (width * height); }
  29.         */
  30. };

  31. class Pentagon_devited: public Pentagon{
  32.         public:
  33.                 int area(){
  34.                         cout << "area of Pentagon is complex, return ";
  35.                         return 0;
  36.                 }
  37. };


  38. int main () {

  39.         //sizeof
  40.         cout << "sizeof: Polygon " << sizeof(Polygon)
  41.              << ";\tRectangle " << sizeof(Rectangle) << endl;

  42.         Rectangle rect;
  43.         Triangle trgl;
  44.         //Pentagon pent;
  45.         Pentagon_devited pent_d;
  46.         Polygon * ppoly1 = &rect;
  47.         Polygon * ppoly2 = &trgl;
  48.         //Polygon * ppoly3 = &pent;
  49.         Polygon * ppoly4 = &pent_d;


  50.         ppoly1->set_values (4,5);
  51.         ppoly2->set_values (4,5);
  52.         //ppoly3->set_values (4,5);
  53.         ppoly4->set_values (4,5);

  54.         cout << rect.area() << endl;
  55.         cout << trgl.area() << endl;
  56.         //cout << pent.area() << endl;
  57.         cout << pent_d.area() << endl;
  58.         return 0;
  59. }
输出
  1. sizeof: Polygon 12; Rectangle 12
  2. 20
  3. 10
  4. 0
  5. area of Pentagon is complex, return 0
这是纯虚函数,函数定义在本class的派生类中
格式为
  1. virtual return-type name( parameter-list ) = 0;
从这个例子可以看出,
  1.  纯虚函数是可以被派生类继承。Polygon <-- Pentagon <-- Pentagon_devited
  2. 含有纯虚函数声明且没有定义(函数体)的不能有构造对象。如果把(//Pentagon pent;)注释打开,会报错:cannot declare variable ‘pent’ to be of abstract type ‘Pentagon’
  3. sizeof增大了,用gdb调试可以发现class内部多出来一个_vptr.Polygon,这是多态实现的结构基础,这是选择具体函数的入口地址。虚拟表的首地址,这方面还没测试,说不清。不过这也是争议较多的地方,个人认为这是那性能换方便,增加不可控因素,增加选择具体入口的成本,鸡肋设计。
irtual function -- 一般虚函数
修改上面的例子为
  1. #include <iostream>
  2. using namespace std;

  3. class Polygon {
  4.         protected:
  5.                 int width, height;
  6.         public:
  7.                 void set_values (int a, int b)
  8.                 { width=a; height=b; }
  9.                 virtual int area() {return 0;}
  10.                 // _vptr.Polygon show difrent value with
  11.                 // the gdb command: p *this
  12.                 // this is a access into virtual table
  13. };

  14. class Triangle: public Polygon {
  15.         public:
  16.                 int area ()
  17.                 { return (width * height / 2); }
  18. };

  19. class Rectangle: public Polygon {
  20.         public:
  21.                 int area ()
  22.                 { return (width * height); }
  23. };

  24. class Pentagon: public Polygon {
  25.         public:
  26.         /*
  27.                 int area ()
  28.                 { return (width * height); }
  29.         */
  30. };

  31. class Pentagon_devited: public Pentagon{
  32.         public:
  33.                 int area(){
  34.                         cout << "area of Pentagon is complex, return ";
  35.                         return 0;
  36.                 }
  37. };


  38. int main () {

  39.         //sizeof
  40.         cout << "sizeof: Polygon " << sizeof(Polygon)
  41.              << ";\tRectangle " << sizeof(Rectangle) << endl;

  42.         Polygon poly ;
  43.         Rectangle rect;
  44.         Triangle trgl;
  45.         Pentagon pent;
  46.         Pentagon_devited pent_d;
  47.         Polygon * ppoly1 = &rect;
  48.         Polygon * ppoly2 = &trgl;
  49.         Polygon * ppoly3 = &pent;
  50.         Polygon * ppoly4 = &pent_d;


  51.         poly.set_values(4,5);
  52.         ppoly1->set_values (4,5);
  53.         ppoly2->set_values (4,5);
  54.         ppoly3->set_values (4,5);
  55.         ppoly4->set_values (4,5);

  56.         cout << poly.area() << endl;
  57.         cout << rect.area() << endl;
  58.         cout << trgl.area() << endl;
  59.         cout << pent.area() << endl;
  60.         cout << pent_d.area() << endl;
  61.         return 0;
  62. }
输出
  1. 0
  2. 20
  3. 10
  4. 0
  5. area of Pentagon is complex, return 0
这是一般虚函数,函数定义在本类中实现,
格式为
  1. virtual return-type name( parameter-list ){...};
我把注释打开,并添加对基类Polygon的具体事例。可以看出几点
  1. 纯虚函数是可以被派生类继承。Polygon <-- Pentagon <-- Pentagon_devited
  2. 含有虚函数声明或者继承虚函数的类可以构造对象。
  3. sizeof同样增大,含有进入_vptr的指针。


有几篇文章摘抄如下
=====================
C 虚拟函数实现多态性分析

面向对象程序设计的基本观点是用程式来仿真大千世界,这使得它的各种根本特性非常人性化,如封装、继承、多态等等,而虚拟函数就是c++中实现多态性的主将。为了实现多态性,c++编译器也革命性地提供了动态联编(或叫晚捆绑)这一特征。

虚拟函数亦是mfc编程的关键所在,mfc编程主要有两种方法:一是响应各种消息,进行对应的消息处理。二就是重载并改写虚拟函数,来实现自己的某些要求或改变系统的某些默认处理。

虚函数的地位是如此的重要,对它进行穷根究底,力求能知其然并知其所以然对我们编程能力的提高大有好处。下面且听我道来。

多态性和动态联编的实现过程分析

一、基础

1、多态性:使用基础类的指针动态调用其派生类中函数的特性。

2、动态联编:在运行阶段,才将函数的调用与对应的函数体进行连接的方式,又叫运行时联编或晚捆绑。

二、过程描述

1、编译器发现一个类中有虚函数,编译器会立即为此类生成虚拟函数表 vtable(后面有对vtable的分析)。虚拟函数表的各表项为指向对应虚拟函数的指针。

2、编译器在此类中隐含插入一个指针vptr(对vc编译器来说,它插在类的第一个位置上)。

有一个办法可以让你感知这个隐含指针的存在,虽然你不能在类中直接看到它,但你可以比较一下含有虚拟函数时的类的尺寸和没有虚拟函数时的类的尺寸,你能够发现,这个指针确实存在。

class cnovirtualfun { private: long lmember; public: long getmembervalue(); } class chavevirtualfun { private: long lmember; public: virtual long getmembervalue(); } cnovirtualfun obj; sizeof(obj) -> == 4; chavevirtualfun obj; sizeof(obj) -> == 8;

3、在调用此类的构造函数时,在类的构造函数中,编译器会隐含执行vptr与vtable的关联代码,将vptr指向对应的vtable。这就将类与此类的vtable联系了起来。

4、在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的this指针,这样依靠此this指针即可得到正确的vtable,从而实现了多态性。在此时才能真正与函数体进行连接,这就是动态联编。

三、vtable 分析

分析1:虚拟函数表包含此类及其父类的所有虚拟函数的地址。如果它没有重载父类的虚拟函数,vtable中对应表项指向其父类的此函数。反之,指向重载后的此函数。

分析2:虚拟函数被继承后仍旧是虚拟函数,虚拟函数非常严格地按出现的顺序在vtable中排序,所以确定的虚拟函数对应vtable中一个固定的位置n,n是一个在编译时就确定的常量。所以,使用vptr加上对应的n,就可得到对应函数的入口地址。

四、编译器调用虚拟函数的汇编码

push funparam;先将函数参数压栈

push si;将this指针压栈,以确保在当前类上操作

mov bx,Word ptr[si];因为vc++编译器将vptr放在类的第一个位置上,所以bx内为vptr

call word ptr[bx+n];调用虚拟函数。n = 所调用的虚拟函数在对应vtable 中的位置

纯虚函数

一、引入原因

1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。

2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual returntype function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

二、纯虚函数实质

1、类中含有纯虚函数则它的vtable表不完全,有一个空位,所以,不能生成对象(编译器绝对不允许有调用一个不存在函数的可能)。在它的派生类中,除非重载这个函数,否则,此派生类的vtable表亦不完整,亦不能生成对象,即它也成为一个纯虚基类。

虚函数与构造、析构函数

1、构造函数本身不能是虚拟函数;并且虚机制在构造函数中不起作用(在构造函数中的虚拟函数只会调用它的本地版本)。

想一想,在基类构造函数中使用虚机制,则可能会调用到子类,此时子类尚未生成,有何后果!?。

2、析构函数本身常常要求是虚拟函数;但虚机制在析构函数中不起作用。

若类中使用了虚拟函数,析构函数一定要是虚拟函数,比如使用虚拟机制调用delete,没有虚拟的析构函数,怎能保证delete的是你希望delete的对象。

虚机制也不能在析构函数中生效,因为可能会引起调用已经被delete掉的类的虚拟函数的问题。

对象切片

向上映射(子类被映射到父类)的时候,会发生子类的vtable完全变成父类的vtable的情况。这就是对象切片。

原因:向上映射的时候,接口会变窄,而编译器绝对不允许有调用一个不存在函数的可能,所以,子类中新派生的虚拟函数的入口在vtable中会被强行“切”掉,从而出现上述情况。

虚拟函数使用的缺点

优点讲了一大堆,现在谈一下缺点,虚函数最主要的缺点是执行效率较低,看一看虚拟函数引发的多态性的实现过程,你就能体会到其中的原因。

------------------------------------------------------------------

C++箴言:考虑可选的虚拟函数的替代方法

现在你工作在一个视频游戏上,你在游戏中为角色设计了一个 hierarchy(继承体系)。你的游戏中有着变化多端的恶劣环境,角色被伤害或者其它的健康状态降低的情况并不罕见。因此你决定提供一个 member function(成员函数)healthValue,它返回一个象征角色健康状况如何的整数。因为不同的角色计算健康值的方法可能不同,将 healthValue 声明为 virtual(虚拟)似乎是显而易见的设计选择:

class GameCharacter {
public:
  virtual int healthValue() const;        // return character's health rating;
  ...                                     // derived classes may redefine this
};

  healthValue 没有被声明为 pure virtual(纯虚)的事实暗示这里有一个计算健康值的缺省算法。

  这确实是一个显而易见的设计选择,而在某种意义上,这是它的缺点。因为这样的设计过于显而易见,你可能不会对它的其它可选方法给予足够的关注。为了帮助你脱离 object-oriented design(面向对象设计)的习惯性道路,我们来考虑一些处理这个问题的其它方法。

  经由非虚拟接口惯用法实现的模板方法模式

  我们以一个主张 virtual functions(虚拟函数)应该几乎总是为 private(私有的)的有趣观点开始。这一观点的拥护者提出:一个较好的设计应该保留作为 public member function(公有成员函数)的 healthValue,但应将它改为 non-virtual(非虚拟的)并让它调用一个 private virtual function(私有虚拟函数)来做真正的工作,也就是说,doHealthValue:

class GameCharacter {
public:
  int healthValue() const               // derived classes do not redefine
  {                                     // this - see Item 36

    ...                                 // do "before" stuff - see below

    int retVal = doHealthValue();       // do the real work

    ...                                 // do "after" stuff - see below

    return retVal;
  }
  ...

private:
  virtual int doHealthValue() const     // derived classes may redefine this
  {
    ...                                 // default algorithm for calculating
  }                                     // character's health
};

  在这个代码(以及本文的其它代码)中,我在类定义中展示 member functions(成员函数)的本体。这会将它们隐式声明为 inline(内联)。我用这种方法展示代码仅仅是这样更易于看到它在做些什么。我所描述的设计与是否 inline 化无关,所以不必深究 member functions(成员函数)定义在类的内部有什么意味深长的含义。根本没有。

  这个基本的设计——让客户通过 public non-virtual member functions(公有非虚拟成员函数)调用 private virtual functions(私有虚拟函数)——被称为 non-virtual interface (NVI) idiom(非虚拟接口惯用法)。这是一个更通用的被称为 Template Method(一个模式,很不幸,与 C++ templates(模板)无关)的 design pattern(设计模式)的特殊形式。我将那个 non-virtual function(非虚拟函数)(例如,healthValue)称为 virtual function's wrapper(虚拟函数的外壳)。

  NVI idiom(惯用法)的一个优势通过 "do 'before' stuff" 和 "do 'after' stuff" 两个注释在代码中标示出来。这些注释标出的代码片断在做真正的工作的 virtual function(虚拟函数)之前或之后调用。这就意味着那个 wrapper(外壳)可以确保在 virtual function(虚拟函数)被调用前,特定的背景环境被设置,而在调用结束之后,这些背景环境被清理。例如,"before" stuff 可以包括锁闭一个 mutex(互斥体),生成一条日志条目,校验类变量和函数的 preconditions(前提条件)是否被满足,等等。"after" stuff 可以包括解锁一个 mutex(互斥体),校验函数的 postconditions(结束条件),类不变量的恢复,等等。如果你让客户直接调用 virtual functions(虚拟函数),确实没有好的方法能够做到这些。

  涉及 derived classes(派生类)重定义 private virtual functions(私有虚拟函数)(这些重定义函数它们不能调用!)的 NVI idiom 可能会搅乱你的头脑。这里没有设计上的矛盾。重定义一个 virtual function(虚拟函数)指定如何做某些事。调用一个 virtual function(虚拟函数)指定什么时候去做。互相之间没有关系。NVI idiom 允许 derived classes(派生类)重定义一个 virtual function(虚拟函数),这样就给了它们控制功能如何实现的能力,但是 base class(基类)保留了决定函数何时被调用的权利。乍一看很奇怪,但是 C++ 规定 derived classes(派生类)可以重定义 private inherited virtual functions(私有的通过继承得到的函数)是非常明智的。

  在 NVI idiom 之下,virtual functions(虚拟函数)成为 private(私有的)并不是绝对必需的。在一些 class hierarchies(类继承体系)中,一个 virtual function(虚拟函数)的 derived class(派生类)实现被期望调用其 base class(基类)的对应物,而为了这样的调用能够合法,虚拟必须成为 protected(保护的),而非 private(私有的)。有时一个 virtual function(虚拟函数)甚至必须是 public(公有的)(例如,polymorphic base classes(多态基类)中的 destructors(析构函数)),但这样一来 NVI idiom 就不能被真正应用。

  经由函数指针实现的策略模式

  NVI idiom 是 public virtual functions(公有虚拟函数)的有趣的可选替代物,但从设计的观点来看,它比装点门也多不了多少东西。毕竟,我们还是在用 virtual functions(虚拟函数)来计算每一个角色的健康值。一个更引人注目的设计主张认为计算一个角色的健康值不依赖于角色的类型——这样的计算根本不需要成为角色的一部分。例如,我们可能需要为每一个角色的 constructor(构造函数)传递一个指向健康值计算函数的指针,而我们可以调用这个函数进行实际的计算:

class GameCharacter;                               // forward declaration

// function for the default health calculation algorithm
int defaultHealthCalc(const GameCharacter& gc);

class GameCharacter {
public:
  typedef int (*HealthCalcFunc)(const GameCharacter&);

  explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
  : healthFunc(hcf)
  {}

  int healthValue() const
  { return healthFunc(*this); }

  ...

private:
  HealthCalcFunc healthFunc;
};

  这个方法是另一个通用 design pattern(设计模式)—— Strategy 的简单应用,相对于基于 GameCharacter hierarchy(继承体系)中的 virtual functions(虚拟函数)的方法,它提供了某些更引人注目的机动性:

  • 相同角色类型的不同实例可以有不同的健康值计算函数。例如:
class EvilBadGuy: public GameCharacter {
public:
  explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc)
  : GameCharacter(hcf)
  { ... }

  ...

};
int loseHealthQuickly(const GameCharacter&);    // health calculation
int loseHealthSlowly(const GameCharacter&);     // funcs with different
                                                // behavior

EvilBadGuy ebg1(loseHealthQuickly);             // same-type charac-
EvilBadGuy ebg2(loseHealthSlowly);              // ters with different
                                                // health-related
                                                // behavior
  • 对于一个指定的角色健康值的计算函数可以在运行时改变。例如,GameCharacter 可以提供一个 member function(成员函数)setHealthCalculator,它被允许代替当前的健康值计算函数。

  在另一方面,健康值计算函数不再是 GameCharacter hierarchy(继承体系)的一个 member function(成员函数)的事实,意味着它不再拥有访问它所计算的那个对象内部构件的特权。例如,defaultHealthCalc 不能访问 EvilBadGuy 的 non-public(非公有)构件。如果一个角色的健康值计算能够完全基于通过角色的 public interface(公有接口)可以得到的信息,这就没什么问题,但是,如果准确的健康值计算需要 non-public(非公有)信息,就会有问题。实际上,在任何一个你要用 class(类)外部的等价机能(例如,经由一个 non-member non-friend function(非成员非友元函数)或经由另一个 class(类)的 non-friend member function(非友元成员函数))代替 class(类)内部的机能(例如,经由一个 member function(成员函数))的时候,它都是一个潜在的问题。这个问题将持续影响本 Item 的剩余部分,因为所有我们要考虑的其它设计选择都包括 GameCharacter hierarchy(继承体系)的外部函数的使用。

  作为一个通用规则,解决对“non-member functions(非成员函数)对类的 non-public(非公有)构件的访问的需要”的唯一方法就是削弱类的 encapsulation(封装性)。例如,class(类)可以将 non-member functions(非成员函数)声明为 friends(友元),或者,它可以提供对“在其它情况下它更希望保持隐藏的本身的实现部分”的 public accessor functions(公有访问者函数)。使用一个 function pointer(函数指针)代替一个 virtual function(虚拟函数)的优势(例如,具有逐对象健康值计算函数的能力和在运行时改变这样的函数的能力)是否能抵消可能的降低 GameCharacter 的 encapsulation(封装性)的需要是你必须在设计时就做出决定的重要部分。

经由 tr1::function 实现的策略模式

  一旦你习惯了 templates(模板)和 implicit interfaces(隐式接口)的应用,function-pointer-based(基于函数指针)的方法看上去就有些死板了。健康值的计算为什么必须是一个 function(函数),而不能是某种简单的行为类似 function(函数)的东西(例如,一个 function object(函数对象))?如果它必须是一个 function(函数),为什么不能是一个 member function(成员函数)?为什么它必须返回一个 int,而不是某种能够转型为 int 的类型?

  如果我们用一个 tr1::function 类型的对象代替一个 function pointer(函数指针)(诸如 healthFunc),这些约束就会消失。这样的对象可以持有 any callable entity(任何可调用实体)(例如,function pointer(函数指针),function object(函数对象),或 member function pointer(成员函数指针)),这些实体的标志性特征就是兼容于它所期待的东西。我们马上就会看到这样的设计,这次使用了 tr1::function:

class GameCharacter;                                 // as before
int defaultHealthCalc(const GameCharacter& gc);      // as before

class GameCharacter {
public:
   // HealthCalcFunc is any callable entity that can be called with
   // anything compatible with a GameCharacter and that returns anything
   // compatible with an int; see below for details
   typedef std::tr1::function HealthCalcFunc;
   explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
   : healthFunc(hcf)
   {}

   int healthValue() const
   { return healthFunc(*this);   }

   ...

private:
  HealthCalcFunc healthFunc;
};

  就像你看到的,HealthCalcFunc 是一个 tr1::function instantiation(实例化)的 typedef。这意味着它的行为类似一个普通的 function pointer(函数指针)类型。我们近距离看看 HealthCalcFunc 究竟是一个什么东西的 typedef:

std::tr1::function<int (const GameCharacter&)>

  这里我突出了这个 tr1::function instantiation(实例化)的“target signature(目标识别特征)”。这个 target signature(目标识别特征)是“取得一个引向 const GameCharacter 的 reference(引用),并返回一个 int 的函数”。这个 tr1::function 类型的(例如,HealthCalcFunc 类型的)对象可以持有兼容于这个 target signature(目标识别特征)的 any callable entity(任何可调用实体)。兼容意味着这个实体的参数能够隐式地转型为一个 const GameCharacter&,而它的返回类型能够隐式地转型为一个 int。

  与我们看到的最近一个设计(在那里 GameCharacter 持有一个指向一个函数的指针)相比,这个设计几乎相同。仅有的区别是目前的 GameCharacter 持有一个 tr1::function 对象——指向一个函数的 generalized(泛型化)指针。除了达到“clients(客户)在指定健康值计算函数时有更大的灵活性”的效果之外,这个变化是如此之小,以至于我宁愿对它视而不见:

short calcHealth(const GameCharacter&);          // health calculation
                                                 // function; note
                                                 // non-int return type

struct HealthCalculator {                        // class for health
  int operator()(const GameCharacter&) const     // calculation function
  { ... }                                        // objects
};

class GameLevel {
public:
  float health(const GameCharacter&) const;      // health calculation
  ...                                            // mem function; note
};                                               // non-int return type


class EvilBadGuy: public GameCharacter {         // as before
  ...
};
class EyeCandyCharacter:   public GameCharacter {  // another character
  ...                                              // type; assume same
};                                                 // constructor as
                                                   // EvilBadGuy


EvilBadGuy ebg1(calcHealth);                       // character using a
                                                   // health calculation
                                                   // function


EyeCandyCharacter ecc1(HealthCalculator());        // character using a
                                                   // health calculation
                                                   // function object

GameLevel currentLevel;
...
EvilBadGuy ebg2(                                   // character using a
  std::tr1::bind(&GameLevel::health,               // health calculation
          currentLevel,                            // member function;
          _1)                                      // see below for details
);

  就个人感觉而言:我发现 tr1::function 能让你做的事情是如此让人惊喜,它令我浑身兴奋异常。如果你没有感到兴奋,那可能是因为你正目不转睛地盯着 ebg2 的定义并对 tr1::bind 的调用会发生什么迷惑不解。请耐心地听我解释。

  比方说我们要计算 ebg2 的健康等级,应该使用 GameLevel class(类)中的 health member function(成员函数)。现在,GameLevel::health 是一个被声明为取得一个参数(一个引向 GameCharacter 的引用)的函数,但是它实际上取得了两个参数,因为它同时得到一个隐式的 GameLevel 参数——指向 this。然而,GameCharacters 的健康值计算函数只取得单一的参数:将被计算健康值的 GameCharacter。如果我们要使用 GameLevel::health 计算 ebg2 的健康值,我们必须以某种方式“改造”它,以使它适应只取得唯一的参数(一个 GameCharacter),而不是两个(一个 GameCharacter 和一个 GameLevel)。在本例中,我们总是要使用 currentLevel 作为 GameLevel 对象来计算 ebg2 的健康值,所以每次调用 GameLevel::health 计算 ebg2 的健康值时,我们就要 "bind"(凝固)currentLevel 来作为 GameLevel 的对象来使用。这就是 tr1::bind 的调用所做的事情:它指定 ebg2 的健康值计算函数应该总是使用 currentLevel 作为 GameLevel 对象。

  我们跳过一大堆的细节,诸如为什么 "_1" 意味着“当为了 ebg2 调用 GameLevel::health 时使用 currentLevel 作为 GameLevel 对象”。这样的细节并没有什么启发性,而且它们将转移我所关注的基本点:在计算一个角色的健康值时,通过使用 tr1::function 代替一个 function pointer(函数指针),我们将允许客户使用 any compatible callable entity(任何兼容的可调用实体)。很酷是不是?

  “经典的”策略模式

  如果你比 C++ 更加深入地进入 design patterns(设计模式),一个 Strategy 的更加习以为常的做法是将 health-calculation function(健康值计算函数)做成一个独立的 health-calculation hierarchy(健康值计算继承体系)的 virtual member function(虚拟成员函数)。做成的 hierarchy(继承体系)设计看起来就像这样:


  如果你不熟悉 UML 记法,这不过是在表示当把 EvilBadGuy 和 EyeCandyCharacter 作为 derived classes(派生类)时,GameCharacter 是这个 inheritance hierarchy(继承体系)的根;HealthCalcFunc 是另一个带有 derived classes(派生类)SlowHealthLoser 和 FastHealthLoser 的 inheritance hierarchy(继承体系)的根;而每一个 GameCharacter 类型的对象包含一个指向“从 HealthCalcFunc 派生的对象”的指针。

  这就是相应的框架代码:

class GameCharacter;                            // forward declaration

class HealthCalcFunc {
public:

  ...
  virtual int calc(const GameCharacter& gc) const
  { ... }
  ...

};

HealthCalcFunc defaultHealthCalc;

class GameCharacter {
public:
  explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)
  : pHealthCalc(phcf)
  {}

  int healthValue() const
  { return pHealthCalc->calc(*this);}

  ...

private:
  HealthCalcFunc *pHealthCalc;
};

  这个方法的吸引力在于对于熟悉“标准的”Strategy pattern(策略模式)实现的人可以很快地识别出来,再加上它提供了通过在 HealthCalcFunc hierarchy(继承体系)中增加一个 derived class(派生类)而微调已存在的健康值计算算法的可能性。

  小结

  本文的基本建议是当你为尝试解决的问题寻求一个设计时,你应该考虑可选的 virtual functions(虚拟函数)的替代方法。以下是对我们考察过的可选方法的一个简略的回顾:

  • 使用 non-virtual interface idiom (NVI idiom)(非虚拟接口惯用法),这是用 public non-virtual member functions(公有非虚拟成员函数)包装可访问权限较小的 virtual functions(虚拟函数)的 Template Method design pattern(模板方法模式)的一种形式。
  • function pointer data members(函数指针数据成员)代替 virtual functions(虚拟函数),一种 Strategy design pattern(策略模式)的显而易见的形式。
  • tr1::function data members(数据成员)代替 virtual functions(虚拟函数),这样就允许使用兼容于你所需要的东西的 any callable entity(任何可调用实体)。这也是 Strategy design pattern(策略模式)的一种形式。
  • virtual functions in another hierarchy(另外一个继承体系中的虚拟函数)代替 virtual functions in one hierarchy(单独一个继承体系中的虚拟函数)。这是 Strategy design pattern(策略模式)的习以为常的实现。

  这不是一个可选的 virtual functions(虚拟函数)的替代设计的详尽无遗的列表,但是它足以使你确信这些是可选的方法。此外,它们之间互为比较的优劣应该使你考虑它们时更为明确。

  为了避免陷入 object-oriented design(面向对象设计)的习惯性道路,时不时地给车轮一些有益的颠簸。有很多其它的道路。值得花一些时间去考虑它们。

  Things to Remember

  • 可选的 virtual functions(虚拟函数)的替代方法包括 NVI 惯用法和 Strategy design pattern(策略模式)的各种变化形式。NVI 惯用法本身是 Template Method design pattern(模板方法模式)的一个实例。
  • 将一个机能从一个 member function(成员函数)中移到 class(类)之外的某个函数中的一个危害是 non-member function(非成员函数)没有访问类的 non-public members(非公有成员)的途径。
  • tr1::function 对象的行为类似 generalized function pointers(泛型化的函数指针)。这样的对象支持所有兼容于一个给定的目标特征的 callable entities(可调用实体)。

=====================
参考
  1. cpp reference
  2. C++虚拟函数实现多态性分析 ------ 
  3. 考虑可选的虚拟函数的替代方法  --
其他文章
阅读(1357) | 评论(0) | 转发(0) |
0

上一篇:c friend -- 友元

下一篇:c/c++ extern “C”

给主人留下些什么吧!~~