标签:
- #include <iostream>
- using namespace std;
- class Polygon {
- protected:
- int width, height;
- public:
- void set_values (int a, int b)
- { width=a; height=b; }
- virtual int area() = 0 ;//{return 0;}
- // _vptr.Polygon show difrent value with
- // the gdb command: p *this
- // this is a access into virtual table
- };
- class Triangle: public Polygon {
- public:
- int area ()
- { return (width * height / 2); }
- };
- class Rectangle: public Polygon {
- public:
- int area ()
- { return (width * height); }
- };
- class Pentagon: public Polygon {
- public:
- /*
- int area ()
- { return (width * height); }
- */
- };
- class Pentagon_devited: public Pentagon{
- public:
- int area(){
- cout << "area of Pentagon is complex, return ";
- return 0;
- }
- };
- int main () {
- //sizeof
- cout << "sizeof: Polygon " << sizeof(Polygon)
- << ";\tRectangle " << sizeof(Rectangle) << endl;
- Rectangle rect;
- Triangle trgl;
- //Pentagon pent;
- Pentagon_devited pent_d;
- Polygon * ppoly1 = ▭
- Polygon * ppoly2 = &trgl;
- //Polygon * ppoly3 = &pent;
- Polygon * ppoly4 = &pent_d;
- ppoly1->set_values (4,5);
- ppoly2->set_values (4,5);
- //ppoly3->set_values (4,5);
- ppoly4->set_values (4,5);
- cout << rect.area() << endl;
- cout << trgl.area() << endl;
- //cout << pent.area() << endl;
- cout << pent_d.area() << endl;
- return 0;
- }
输出
- sizeof: Polygon 12; Rectangle 12
- 20
- 10
- 0
- area of Pentagon is complex, return 0
这是纯虚函数,函数定义在本class的派生类中格式为
- virtual return-type name( parameter-list ) = 0;
从这个例子可以看出,
- 纯虚函数是可以被派生类继承。Polygon <-- Pentagon <-- Pentagon_devited
- 含有纯虚函数声明且没有定义(函数体)的不能有构造对象。如果把(//Pentagon pent;)注释打开,会报错:cannot declare variable ‘pent’ to be of abstract type ‘Pentagon’
- sizeof 增大了,用gdb调试可以发现class内部多出来一个_vptr.Polygon,这是多态实现的结构基础,这是选择具体函数的入口地址。虚拟表的首地 址,这方面还没测试,说不清。不过这也是争议较多的地方,个人认为这是那性能换方便,增加不可控因素,增加选择具体入口的成本,鸡肋设计。
- #include <iostream>
- using namespace std;
- class Polygon {
- protected:
- int width, height;
- public:
- void set_values (int a, int b)
- { width=a; height=b; }
- virtual int area() {return 0;}
- // _vptr.Polygon show difrent value with
- // the gdb command: p *this
- // this is a access into virtual table
- };
- class Triangle: public Polygon {
- public:
- int area ()
- { return (width * height / 2); }
- };
- class Rectangle: public Polygon {
- public:
- int area ()
- { return (width * height); }
- };
- class Pentagon: public Polygon {
- public:
- /*
- int area ()
- { return (width * height); }
- */
- };
- class Pentagon_devited: public Pentagon{
- public:
- int area(){
- cout << "area of Pentagon is complex, return ";
- return 0;
- }
- };
- int main () {
- //sizeof
- cout << "sizeof: Polygon " << sizeof(Polygon)
- << ";\tRectangle " << sizeof(Rectangle) << endl;
- Polygon poly ;
- Rectangle rect;
- Triangle trgl;
- Pentagon pent;
- Pentagon_devited pent_d;
- Polygon * ppoly1 = ▭
- Polygon * ppoly2 = &trgl;
- Polygon * ppoly3 = &pent;
- Polygon * ppoly4 = &pent_d;
- poly.set_values(4,5);
- ppoly1->set_values (4,5);
- ppoly2->set_values (4,5);
- ppoly3->set_values (4,5);
- ppoly4->set_values (4,5);
- cout << poly.area() << endl;
- cout << rect.area() << endl;
- cout << trgl.area() << endl;
- cout << pent.area() << endl;
- cout << pent_d.area() << endl;
- return 0;
- }
输出
- 0
- 20
- 10
- 0
- area of Pentagon is complex, return 0
这是一般虚函数,函数定义在本类中实现,格式为
- virtual return-type name( parameter-list ){...};
我把注释打开,并添加对基类Polygon的具体事例。可以看出几点
- 纯虚函数是可以被派生类继承。Polygon <-- Pentagon <-- Pentagon_devited
- 含有虚函数声明或者继承虚函数的类可以构造对象。
- sizeof同样增大,含有进入_vptr的指针。
面向对象程序设计的基本观点是用程式来仿真大千世界,这使得它的各种根本特性非常人性化,如封装、继承、多态等等,而虚拟函数就是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 }; |
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 }; |
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; }; |
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 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; }; |
std::tr1::function<int (const GameCharacter&)> |
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 ); |
如果你不熟悉 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; }; |
这不是一个可选的 virtual functions(虚拟函数)的替代设计的详尽无遗的列表,但是它足以使你确信这些是可选的方法。此外,它们之间互为比较的优劣应该使你考虑它们时更为明确。
为了避免陷入 object-oriented design(面向对象设计)的习惯性道路,时不时地给车轮一些有益的颠簸。有很多其它的道路。值得花一些时间去考虑它们。
Things to Remember
标签:
原文地址:http://www.cnblogs.com/timdes/p/4781111.html