条款34、区分接口继承和实现继承

	public继承由两部分组成:函数接口继承和函数实现继承	设计类时,我们有不同的需求:1、Derived只继承成员函数的接口(也就是声明) 2、Derived同时继承接口和实现,但又能覆写继承的实现(override)  3、Derived同时继承接口和实现,但不允许覆写任何东西。
为表现上面的差异,考虑以下代码:
class Shape
{
public:
         virtual void draw() const=0;
         virtual void error(const std::string& msg);
         int objectID() const;
         ….
};
class Rectangle: public Shape{….};
class Eclipse:public Shape{….};

Shape是个抽象类,因为具有纯虚函数draw。故只能创建其派生类实体。尽管如此,Shape还是强烈影响了所有以public形式继承它的派生类。
a、成员函数接口总是会被继承。如TK32,可施行于基类的函数也可施行于派生类中。
声明的三个不同函数:draw 纯虚   error 虚函数  objectID非虚函数。首先考虑draw:
class Shape
{
public:
         virtualvoid draw() const=0;
         ….
};

pure virtual两个最突出的特性:1)、必须被任何“继承了它们”的具象类重新声明  2)、在抽象类的通常没有定义。结合这两性质得出:
1、声明为纯虚函数的目的是为了让派生类只继承其接口
这对Shape::draw再合理不过,因为任何shape对象都应该是可绘出的。相当于对设计者说:你必须提供一个draw函数,但我不干涉你怎么实现它。
意外的是,我们也可以为纯虚函数提供定义。编译器也不会报错,但调用的唯一途径是:调用时明确指出其class。如下:
Shape* ps=new Shape;   //F  Shape为抽象的
Shape* ps1=new Rectangle; //T
ps1->draw();     //T 调用Rectangle::draw
Shape* ps2=new Eclipse; //T
ps2->draw();   //T   Eclipse::draw
ps1->Shape::draw(); //T  Shape::draw
ps2->Shape::draw(); //T  Shape::draw

非纯虚函数和纯虚函数有点点不同:派生类继承其函数接口,但impure virtual函数会提供一份实现代码。派生类可能覆写它。
!声明非纯虚函数的目的,是让派生类继承该函数的接口和缺省实现。考虑Shape::error
class Shape
{
public:
         virtualvoid error(const std::string& msg);
         ….
};

接口表示:每个类必须支持一个error函数,但可自由处理错误。如果不想做出任何特殊行为,可以调用Shape类提供的缺省版本

允许非纯虚函数同时指定函数声明和缺省行为,却也可能造成危险。考虑以下例子:
如下飞机继承体系,A 、B两种飞机以相同方式飞行。
class Airport{….};  //机场
clss Airplane
{
<span style="white-space:pre">	</span>public:
<span style="white-space:pre">	</span>virtual void fly virtual void fly(const Airport&  des);
};
void fly{const Airport&  des}

{缺省行为,飞至指定目的地 ;}
class A: public Airplane{….};
class B: public Airplane{….};

所有飞机能飞,但需要不同表现,Airplane:fly声明为virtual。避免AB中代码重复,提供缺省代码,同时被AB继承。
这是典型的面向对象设计:将共同性质至于其类。突出共性,避免代码重复

	现在引进C类飞机,和AB飞行方式不同,但急于上线新飞机,竟忘记定义其fly,于是出现下面代码:
class C: public Airplane{….}; //未声明fly
Airport*  P;   //机场P
Airplane* Pa=new C;
pa->fly(p;)   //调用Airplane::fly

很明显这会造成灾难,问题不在于缺省行为,而是在未说明“需要”的情况下就继承了该行为。有简单的方法可以解决此问题,即分离虚函数接口和缺省实现。如下代码:
class Airport{….};  //机场
clss Airplane
{
public:
         virtual void fly(const  Airport&  des) =0;
         …
protected:
         void defaultFly(const  Airport& des);
};

void  Airplane::defaultFly(const  Airport& des){缺省行为,飞至指定目的地 ;}
需要注意的是,Airplane::fly已声明为一个纯虚函数,只提供飞行接口。缺省行为也出现在类中。AB若想使用此行为,可以在fly中做一个inline调用。如下:
class A: public Airplane
{
public:
         virtualvoid fly(const Airport& des)
         {Airplane:: defaultFly(des); }
         …
}
class B: public Airplane
{
public:
         virtualvoid fly(const Airport& des)
         {Airplane:: defaultFly(p); }
         ….
}

这样C就不可能意外继承函数体了,因为纯虚函数迫使C必须提供自己的fly版本。
class C: public Airplane
{
public:
         virtual void fly(constAirport& des)
         ….
}
void C::fly(const Airport& des)
{自定义版本;};


Airplane::defaultFly版本被声明protected,因为它是Aiplane和其派生类的实现细节,我们只在间能不能飞而不会在意怎么飞。同时Airplane::defaultFly是非虚函数也很重要,因为没有任何派生类应该重新定义此函数。如果定义为虚函数,又会出现一个循环问题:如果派生类又忘记重新定义defaultFly会怎样?
有些人反对以不同的函数分别提供接口和实现(过度相同的函数引起命名空间污染)。但同意接口和实现应该分开。我们可以利用:纯虚函数必须在Drived类中重新声明,但它们也可以有自己的实现。如下代码:
<pre name="code" class="cpp">class Airport{….};   //机场
clss Airplane
{
public:
	virtual void fly(const  Airport&  des) =0;
	…
};
void Airplane::fly(const Airport& des)   //纯虚函数的实现
{
	缺省行为,飞向指定目的地;
}
class A: public Airplane
{
public:
	virtual void fly(const Airport& des)
	{Airplane::fly(des); }
	…
}
class B: public Airplane
{
public:
	virtual void fly(const Airport& des)
	{ Airplane::fly(des);; }
	….
};
class C: public Airplane
{
public:
	virtual void fly(const Airport& des)
	….
}
void C::fly(const Airport& des)
{
	自定义飞行行为;
}


这个设计和前面基本一样,只是pure virtual函数Airplane::fly代替了独立函数Airplane::defaultFly.本质上分为两要素:声明部分是接口,定义部分是缺省实现。
再看Shape的non-virtual函数:
class Shape
{
public:
         int objectID() const;
…
};

成员函数是非虚函数,意味着并不打算在派生类中有不同行为。实际上非虚成员函数表现的不变性凌驾其特意性。
声明非虚函数的目的:让Drived类继承函数接口和一份强制实现。
 

	如objectID函数,每个对象都有产生识别码的函数,且计算方法相同。任何派生类不应去改变此行为,所以它不应被重定义。
纯虚、虚、非虚三类函数使得能精确指定想要派生类继承的东西。但也会犯一些错误:
	1、将所有函数声明为非虚,这使得派生类没机会做特化工作。想作为基类的任何类,都会有若干虚函数。如果担心虚函数成本,2-8法则说明,平均而言函数调用中有80%是虚函数不冲击的大体效率。应将精力先至于20%的关键代码上。
	2、另一个常见错误是全部声明为虚函数。当然有些是正确的,如接口类。这样设计时要确定不变性凌驾于特意性。

需要记住的:
	1、接口继承和实现继承不同。在public继承之下,派生类总是继承基类的接口。
	2、纯虚函数只具体指定接口继承。
	3、非纯虚函数具体指定接口继承和缺省实现继承。
	4、非虚函数具体指定接口继承和强制实现继承。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值