条款34、区分接口继承和实现继承
来源:互联网 发布:录音整理软件 免费 编辑:程序博客网 时间:2024/05/23 01:53
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; //Tps1->draw(); //T 调用Rectangle::drawShape* ps2=new Eclipse; //Tps2->draw(); //T Eclipse::drawps1->Shape::draw(); //T Shape::drawps2->Shape::draw(); //T Shape::draw
非纯虚函数和纯虚函数有点点不同:派生类继承其函数接口,但impure virtual函数会提供一份实现代码。派生类可能覆写它。
!声明非纯虚函数的目的,是让派生类继承该函数的接口和缺省实现。考虑Shape::errorclass 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,于是出现下面代码:这个设计和前面基本一样,只是pure virtual函数Airplane::fly代替了独立函数Airplane::defaultFly.本质上分为两要素:声明部分是接口,定义部分是缺省实现。class C: public Airplane{….}; //未声明flyAirport* P; //机场PAirplane* 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){自定义飞行行为;}
再看Shape的non-virtual函数:
class Shape{public: int objectID() const;…};
成员函数是非虚函数,意味着并不打算在派生类中有不同行为。实际上非虚成员函数表现的不变性凌驾其特意性。
声明非虚函数的目的:让Drived类继承函数接口和一份强制实现。
如objectID函数,每个对象都有产生识别码的函数,且计算方法相同。任何派生类不应去改变此行为,所以它不应被重定义。纯虚、虚、非虚三类函数使得能精确指定想要派生类继承的东西。但也会犯一些错误:1、将所有函数声明为非虚,这使得派生类没机会做特化工作。想作为基类的任何类,都会有若干虚函数。如果担心虚函数成本,2-8法则说明,平均而言函数调用中有80%是虚函数不冲击的大体效率。应将精力先至于20%的关键代码上。2、另一个常见错误是全部声明为虚函数。当然有些是正确的,如接口类。这样设计时要确定不变性凌驾于特意性。
需要记住的:1、接口继承和实现继承不同。在public继承之下,派生类总是继承基类的接口。2、纯虚函数只具体指定接口继承。3、非纯虚函数具体指定接口继承和缺省实现继承。4、非虚函数具体指定接口继承和强制实现继承。
0 0
- 条款34:区分接口继承和实现继承
- 条款34:区分接口继承和实现继承
- 条款34:区分接口继承和实现继承
- [effective c++]条款34-区分接口继承和实现继承
- 条款34:区分接口继承和实现继承
- 条款34:区分接口继承和实现继承
- Effective C++ 条款34 区分接口继承和实现继承
- 条款34:区分接口继承和实现继承
- Effective C++:条款34:区分接口继承和实现继承
- 条款34:区分接口继承和实现继承
- 《Effective C++》:条款34:区分接口继承和实现继承
- 条款34、区分接口继承和实现继承
- 条款34 区分接口继承和实现继承
- 条款34:区分接口继承和实现继承
- 条款34:区分接口继承和实现继承
- effective c++ 条款36: 区分接口继承和实现继承
- 条款 36: 区分接口继承和实现继承
- 条款34:区分接口继承与实现继承
- mac下gdb签名认证
- UITableView,UICollectionView顶部预留空白的处理
- 颤抖了吗?九步逆向破解银行安全令牌
- shell编程实例
- HTTP状态码
- 条款34、区分接口继承和实现继承
- 高性能 TCP Socket连接关闭释放集中调度类
- 编译Android Framework下的资源文件
- LayoutInflater的inflate方法引起的listview界面布局问题
- tomcat8.X.X的 配置记录
- 关于Struts2上传文件的最大Size的设置
- 【树结构】 无根树转有根树
- Java线程:新特征-原子量
- 卖家新神器:淘宝宝贝复制专家。可以复制淘宝、天猫上的宝贝到自家店里卖。分分钟克隆店铺。谁要,请留言你要克隆的店铺或宝贝地址!