以前C#开发过程中,处理错误常会采用TRY…CATH。SQL
Server新版本中(2005要么重晚)也供有其一处理逻辑错误的布局。可参照下面语法: 

  
多态(Polymorphism)是面向对象的中心概念,本文为C++为例,讨论多态的切实落实。C++中多态可以分为基于继承与虚函数的动态多态以及基
于模板的静态多态,如果无专门指明,本文中起的多态都是乘前者,也就是基于继承与虚函数的动态多态。至于什么是多态,在面向对象中争使用多态,使用
多态的好处等等问题,如果大家感谢兴趣的话,可以查找本面向对象的书写来探望。
    为了有利于说明,下面举一个简短的采取多态的事例(From [1] ):

BEGIN TRY
    –此处写T-SQL代码
END TRY
BEGIN CATCH
    –此处写Error处理代码。
END CATCH

class Shape
{
protected:
  int m_x;    // X coordinate
  int m_y;  // Y coordinate
public:
  // Pure virtual function for drawing
  virtual void Draw() = 0;  

 

  // A regular virtual function
  virtual void MoveTo(int newX, int newY);

 演示,使用http://www.cnblogs.com/insus/articles/1929921.html这个约束,性别字段只能输入1暨3整型数值,下例尝试输入一个不是规则的数值,比如输入5:

 // Regular method, not overridable.
  void Erase();

BEGIN TRY
    INSERT [dbo].[Users] VALUES (‘Insus.NET’,5);
END TRY
BEGIN CATCH
    print N’性别字段只能输入1到3频值。’;
END CATCH

  // Constructor for Shape
  Shape(int x, int y); 

 

 // Virtual destructor for Shape
  virtual ~Shape();
};

施行结果以会见废弃来CATCH块的音讯:

// Circle class declaration
class Circle : public Shape
{
private:
   int m_radius;    // Radius of the circle
public:
   // Override to draw a circle
   virtual void Draw();    

 BWIN必赢亚洲56.net 1

   // Constructor for Circle
   Circle(int x, int y, int radius);

 

  // Destructor for Circle
   virtual ~Circle();
};

// Shape constructor implementation
Shape::Shape(int x, int y)
{
   m_x = x;
   m_y = y;
}
// Shape destructor implementation
Shape::~Shape()
{
//…
}

 // Circle constructor implementation
Circle::Circle(int x, int y, int radius) : Shape (x, y)
{
   m_radius = radius;
}

// Circle destructor implementation
Circle::~Circle()
{
//…
}

// Circle override of the pure virtual Draw method.
void Circle::Draw()
{
   glib_draw_circle(m_x, m_y, m_radius);
}

main()
{
  // Define a circle with a center at (50,100) and a radius of 25
  Shape *pShape = new Circle(50, 100, 25);

  // Define a circle with a center at (5,5) and a radius of 2
  Circle aCircle(5,5, 2);

  // Various operations on a Circle via a Shape pointer
  //Polymorphism
  pShape->Draw();
**  pShape->MoveTo(100, 100);

**  pShape->Erase();
  delete pShape;

 // Invoking the Draw method directly
  aCircle.Draw();
}   

    
例子中以到多态的代码以黑体标有了,它们一个颇明显的特征就是通过一个基类的指针(或者引用)来调用不同子类的办法。
    
那么,现在之题目是,这个力量是何许贯彻之为?我们得先行来大概猜一下:对于一般的不二法门调用,到了汇编代码这同一层次的时光,一般都是利用
Call funcaddr
这样的指令进行调用,其中funcaddr是若调用函数的地方。按理来说,当自己使用指针pShape来调用Draw的时段,编译器应该用
Shape::Draw的地方给给funcaddr,然后Call
指令就足以一直调用Shape::Draw了,这便跟用pShape来调用Shape::Erase一样。但是,运行结果也告知我们,编译器赋给
funcaddr的价值也是Circle::Drawde的价。这即证实,编译器在对照Draw方法和Erase方法时采取了双重标准。那么究竟是哪位出诸如此类
大之佛法,使编译器这个铁面无私的判官都如另外眼相扣呢?virtual!!
    

Clever!!正是virtual这个第一字一手导演了立即无异起“乾坤大挪移”的好戏。说道这里,我们先要明了两个概念:静态绑定和动态绑定。
    1、静态绑定(static
bingding),也深受早期绑定,简单的话即使是编译器在编译期间就是显著知道所要调用的方式,并将拖欠方式的地点与给了Call指令的funcaddr。因此,运行中一直动用Call指令就可调用到相应的点子。
    2、动态绑定(dynamic
binding),也被晚期绑定,与静态绑定不同,在编译期间,编译器并无可知显了解究竟要调用的凡啦一个办法,而及时,要懂得运行期间采用的有血有肉是哪个目标才会说了算。
   
好了,有了当下有限只概念之后,我们就算好说,virtual的意向就是是报编译器:我若拓展动态绑定!编译器当然会注重您的见识,而且以好你是要求,
编译器还要举行过多的政工:编译器自动在声明了virtual方法的类吃插入一个指南针vptr和一个数据结构VTable(vptr用以指向
VTable;VTable是一个指针数组,里面存着函数的地方),并保管二者遵守下面的规则:
   
1、VTable中不得不存放声明也virtual的点子,其它措施无克存于中间。在地方的例证中,Shape的VTable中不怕惟有
Draw,MoveTo与~Shape。方法Erase的地方并无可知存放于VTable中。此外,如果措施是纯虚函数,如
Draw,那么同样如果当VTable中保留相应的职务,但是由于纯虚函数没有函数体,因此该职位被并无存Draw的地址,而是可以选取存放一个错处理
的函数的地点,当该职位给意外调用时,可以据此出错函数进行对应的处理。
   
2、派生类的VTalbe中著录之打基类中持续下来的虚函数地址的索引号必须与该虚函数在基类VTable中之索引号保持一致。如以上例中,如果在
Shape的VTalbe中,Draw为 1 号, MoveTo 2 号,~Shape为 3
号,那么,不管这些点子以Circle中凡是准什么顺序定义之,Circle的VTable中还要管Draw为
1 号,MoveTo为 2哀号。至于
3哀号,这里是~Circle。为什么非是~Shape啊?嘿嘿,忘啦,析构函数不会见连续的。
   
3、vptr是由编译器自动插入生成的,因此编译器必须担负吗那个开展初始化。初始化的时刻选择在目标创建时,而地方就是在构造函数中。因此,编译器必须保证每个接近至少发生一个构造函数,若没有,自动为那个蛮成一个默认构造函数。
     4、vptr通常在对象的开场处,也尽管是Addr(obj) == Addr(obj.vptr)。
   
你看,天下果然没有免费的午宴,为了促成动态绑定,编译器要啊咱不动声色干了如此多的粗话累活。如果您想感受一下编译器的日晒雨淋,那么可以品尝用C语言模拟一
下点的一言一行,【1】中便发出这样一个例证。好了,现在万事具备,只欠东风了。编译,连接,载入,GO!当程序执行到
pShape->Draw()的当儿,上面的设施为初步自作用了。。
   
前面已经干,晚期绑定时用不克确定调用哪个函数,是盖具体的对象不确定。好了,当运行至pShape->Draw()时不时,
对象下了,它由pShape指针标出。我们找到这目标后,就好找到它们其中的vptr(在对象的起始处),有了vptr后,我们就是找到了
VTable,调用的函数就以前方了。。等等,VTable中方法那么基本上,我到底以谁也?不用着急,编译器早已为咱搞好了笔录:编译器在开创
VTable时,已经为每个virtual函数安排好了座次,并且将这个索引号记录了下去。因此,当编译器解析及pShape->Draw()的时节,它就暗的拿函数的名用索引号来替了。这时候,我们通过这个索引号就可于VTable中获得一个函数地址,Call
it!
   
在此处,我们就体会至为何会有第二修规定了,通常,我们都是为此基类的指针来引用派生类的靶子,但是无论具体目标是哪个派生类的,我们都可采取同一之索引号来博取相应的函数实现。
    
现实中产生一个例子其实跟此蛮像的:报警电话来110,119,120(VTable中不同的主意)。不同地方的人头转打不同的号码所发出的结果尚且是休一致
的。譬如,在三绕外之一个总人口(具体目标)跟同围绕内的一个人口(另外一个现实目标)打119,最后调用的消防队肯定是勿雷同的,这便是多态了。这是怎么落实之
呢,每个人还懂得一个报警中心(VTable,里面来三只法子
110,119,120)。如果三缠外之一个口索要火警抢险(一个有血有肉对象)时,它便拨打119,但是他迟早不清楚最后是呀一个消防队会起的。这得生回报
警中心来支配,报警中心经过之现实对象(例子中虽是具体位置了)以及他说拨打的电话号码(可以了解成索引号),报警中心可确定相应调度哪一个消防队进
行抢险(不同之动作)。
    
这样,通过vptr和VTable的帮助,我们就算兑现了C++的动态绑定。当然,这仅是单继承时的情,多又继承的拍卖要相对复杂一点,下面简要说一下
最简便易行的多级继承的状态,至于虚继承的状态,有趣味的恋人可以看
Lippman的《Inside the C++ Object
Model》,这里小虽无进行了。(主要是友善还没有将明白,况且现在大抵又继承都小用了,虚继承应用的空子就还不见了)
    
首先,我要是先期说一下多重继承下对象的内存布局,也就是说该对象是怎么样存放本身的数码的。

class Cute
{
public:
 int i;
 virtual void cute(){ cout<<“Cute cute”<<endl; }
};

class Pet
{
public:
   int j;
   virtual void say(){ cout<<“Pet say”<<endl;  }
};

class Dog : public Cute,public Pet
{
public:
 int z;
 void cute(){ cout<<“Dog cute”<<endl; }
 void say(){ cout<<“Dog say”<<endl;  }
};

   
在点是例子中,一个Dog对象在内存中的布局如下所示:                    

Dog

Vptr1

Cute::i

Vptr2

Pet::j

Dog::z

    
也就是说,在Dog对象吃,会在个别个vptr,每一个跟所继承的父类相对应。如果我们只要想实现多态,就不能不在对象吃标准地找到相应的vptr,以调用不
同的措施。但是,如果冲单继承时的逻辑,也尽管是vptr放在指针指为位置的开局处,那么,要在多重复继承情况下促成,我们不能不确保在用一个差生类的指针隐
式或者显式地换成一个父类的指针时,得到的结果对相应派生类数据以Dog对象中的苗子位置。幸好,这工作编译器已经帮咱成功了。上面的例证中,如果
Dog向上转换成Pet的言语,编译器会活动计算Pet数据以Dog对象中之偏移量,该偏移量加上Dog对象的胚胎位置,就是Pet数据的实在地址了。

int main()
{
 Dog* d = new Dog();
 cout<<“Dog object addr : “<<d<<endl;
 Cute* c = d;
 cout<<“Cute type addr : “<<c<<endl;
 Pet* p = d;
 cout<<“Pet type addr : “<<p<<endl;
 delete d;
}

output:
Dog object addr : 0x3d24b0
Cute type addr : 0x3d24b0
Pet type addr : 0x3d24b8   //
正好指向Dog对象的vptr2处,也就算是Pet的数

     
好了,既然编译器帮我们机关就了不同父类的地方转换,我们调用虚函数的长河吧就算跟单继承统一起来了:通过切实对象,找到vptr(通常指针的胚胎位置,
因此Cute找到的是vptr1,而Pet找到的凡vptr2),通过vptr,我们找到VTable,然后因编译时取得的VTable索引号,我们取
得相应的函数地址,接着便得马上调用了。

     
在此,顺便也领到一下片单特殊的章程在多态中之特别之处吧:第一只是构造函数,在构造函数中调用虚函数是不会见生多态行为之,例子如下:

class Pet
{
public:
   Pet(){ sayHello(); }
   void say(){ sayHello(); }

   virtual void sayHello()
   {
     cout<<“Pet sayHello”<<endl;
   }
  
};

class Dog : public Pet
{
public:
   Dog(){};
   void sayHello()
   {
     cout<<“Dog sayHello”<<endl;
   }
};

int main()
{
 Pet* p = new Dog();
 p->sayHello();
 delete p;
}

output:
Pet sayHello //直接调用的是Pet的sayHello()
Dog sayHello //多态

    
第二单就是是析构函数,使用多态的时光,我们经常使用基类的指针来引用派生类的靶子,如果是动态创建的,对象下完毕后,我们以delete来放对象。但是,如果我们无注意的言辞,会生竟的状况时有发生。

class Pet
{
public:
   ~Pet(){ cout<<“Pet destructor”<<endl;  }
  //virtual ~Pet(){ cout<<“Pet virtual destructor”<<endl; 
}
};

class Dog : public Pet
{
public:
   ~Dog(){ cout<<“Dog destructor”<<endl;};
   //virtual ~Dog(){ cout<<“Dog virtual destructor”<<endl; 
}
};

int main()
{
 Pet* p = new Dog();
 delete p;
}

output:
Pet destructor  //糟了,Dog的析构函数没有调用,memory leak!

比方我们拿析构函数改化virtual以后,结果如下
Dog virtual destructor
Pet virtual destructor   // That’s OK!

   
所以,如果一个近乎设计用来叫延续的话,那么其的析构函数应该为声称也virtual的。

Reference:
[1] Comparing C++ and C (Inheritance and Virtual
Functions)  
[2]
C++对象布局和多态实现的探索 
[3] Multiple inheritance and the this
pointer 讲述多复继承下的类型转换问题

[4] Memory Layout for Multiple and Virtual
Inheritance 详细描述了大多再次菱形多再次继承下之靶子内存布局以及类型转换

 转
http://hi.baidu.com/daping\_zhang/blog/item/e87163d06c42818fa0ec9cfc.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图