Effective C++(条款32-34)

条款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::mf1
d.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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值