Effective C++(条款32-34)

来源:互联网 发布:好用的精华液 知乎 编辑:程序博客网 时间:2024/06/08 21:52

条款32:确定你的public继承塑模出is-a关系

public 继承意味着is-a。适用于基类身上的每一件事情也适用于继承类身上,因为每一个继承类对象也是一个基类对象。继承类比基类表现出更加特殊化的概念,基类则比继承类更加一般化。


思考:正方形与矩形之间的关系是is-a的关系吗?

考虑这段代码:

class Rectangle{public:      virtual void setHeight(int newHeight);      virtual void setWidth(int newWidth);      virtual int Height()const;//返回当前值      virtual int Width()const;      ...};void makeBigger(Rectangle& r){      int oldHeight=r.Height();      r.setWidth(r.Width()+10);      assert(r.Height()==oldHeight);//判断r的高度是否未曾改变}

显然,上述的assert结果永远为真。现在考虑这段代码,其中使用public继承,允许将正方形视为矩形。

class Square:public Rectangle{...};Square s;...assert(s.Width()==s.Heigh());makeBigger(s);assert(s.Width()==s.Heigh());

现在我们遇上了一个问题,在调用makeBigger之前,s的高度和宽度是相同的;在makeBigger函数内,s的宽度改变,但高度不变;在makeBigger返回后,s的豪赌和宽度相同。(注意s是以引用的方式传递给makeBigger的,所以函数修改的是s本身,不是其副本)。

这样的代码,编译器会让你通过,但并不保证程序的行为正确。代码通过编译并不表示就可以正确运行。这样的继承有可能接近事实真相,但也有可能不。所以,你应该确定你确实塑造了解这些个“类的相互关系”之间的差异,并知道如何在C++中最好地塑造他们



条款33:避免遮掩继承而来的名称

C++的名称遮掩规则所做的唯一的事情就是:遮掩名称,置于名称是否应该是相同或不同的类型,并不重要

因为继承类继承了基类内的所有东西,实际运作方式是,继承类作用域嵌套在基类作用域内,继承类内的名称会遮掩基类内的名称,在public继承机制下没有人会希望如此。

class base{private:      int x;public:      virtual void mf1()=const;      virtual void mf2(int);      virtual void mf2();      void mf3();      void mf3(double);      ...};class derived:public base{public:      virtual void mf1();      void mf3();      void mf4();      ...};

这段代码表示,不论参数类型是否相同,函数是否为virtual函数,以作用域为基础的“名称遮掩规则”并没有改变,base class内所有名为mf1和mf3的函数都被derived class内的mf1和mf3函数遮掩掉了。从名称查找观点来看,base::mf1和base::mf3不再被derived继承

解决:为了让遮掩的名称再见天日,可再用using 声明式或转交函数


1、using声明: 

class base{private:      int x;public:      virtual void mf1()=const;      virtual void mf2(int);      virtual void mf2();      void mf3();      void mf3(double);      ...};class derived:public base{public:      using base::mf1;      using base::mf3;//让基类名为mf1、mf3的所有东西在继承类作用域内可见      virtual void mf1();      void mf3();      void mf4();      ...};

这意味着如果你继承基类并加上一些重载函数,而你又希望重新定义或覆写其中一部分,那么你就必须为那些原本会被遮掩的每个名称引入一个using声明式,否则某些你希望继承的名称会被遮掩。


2、使用转交函数

class base{public:      virtual void mf1()=0;      virtual void mf1(int);      ...};class derived:private base{public:      virtual void mf1()      {        base::mf1();      }      ...};...derived d;int x;d.mf1();//调用derived::mf1d.mf1(x);//错误!base::mf1(double)版本被遮掩了。使用using 声明会对base的mf1所有同名版本引入derived class中


这就是继承和名称遮掩的完整故事,但是当继承集合模板,我们又将面对“继承名称被遮掩”的一个全然不同的形式。会有接下来的条款予以说明。





条款34:区别接口继承和实现继承

接口继承和实现继承不同,在public继承下,继承类总是继承基类的接口(is-a关系);

1、pure virtual 函数使基类成为一个抽象类,所以客户不能创建类的实体,只能创建其继承类的实体。

纯虚函数有两个最突出的特性:他们必须被任何“继承了他们”的具象class重新声明,而且他们在抽象class中通常没有定义。

所以,声明一个pure virtual函数的目的就是为了让继承类只继承函数接口


2、声明简单的virtual函数的目的,是让继承类继承该函数的接口和缺省实现

换句话说,就是你应该支持一个virtual函数,但如果你不想自己写一个,可以使用基类提供的缺省版本。


但是,允许virtual函数同时指定函数声明和函数缺省行为,却有可能造成危险,比如在继承类中的此virtual函数还没有明白说明自己要做什么的情况下就继承类该缺省行为。我们可以切断“virtual函数接口”和“缺省实现”之间的连接。

class Airplane{public:      virtual void fly(const Airport& des)=0;      ...};void Airplane::fly(const Ariport& des){      缺省行为,将飞机飞至指定的目的地}class ModelA:public Airplane{public:      virtual void fly(const Airport& des){      Airplane::fly(des);      }      ...};class ModelB:public Airplane{public:      virtual void fly(const Airport& des){     Airplane::fly(des);      }       ...};class ModelC:public Airplane{public:      virtual void fly(const Airport& des);      ...};void ModelC::fly(const Airport& des){      将C型飞机飞至指定的目的地}

现在fly被分割为两个基本要素:其声明部分表现的是接口(那是继承类必须使用的),其定义部分则表现出缺省行为(那是继承类可能使用的,但只有在他们明确提出申请时才是)。



3、声明non-virtual函数的目的是为了令继承类继承函数的接口和一份强制性实现

non-virtual函数代表的意义是不变形凌驾特异性,它绝不该在继承类中被重新定义。


0 0
原创粉丝点击