Effective C++条款32:继承与面向对象——确定你的public继承塑模出is-a关系

一、“is-a”的概念

  • 以C++进行面向对象编程,最重要的一个规则是:public inheritance(公开继承)意味“is-a”(是一种)的关系
  • 如果你令class D以public形式继承class B,你便是告诉编译器:
    • 每一个类型为D的对象同时也是一个类型为B的对象。反之不是
    • B对象可使用的地方,D对象一样可以使用。反之不是

演示案例

  • 下面Student类public继承于Person
class Person {};

class Student :public Person {};
  • 任何获得类型为Person(pointer-to-Person或reference-to-Person)的实参,都可以接受一个Student(pointer-to-Student或reference-to-Student)对象
  • 例如:
void eat(const Person& p);

void study(const Student& s);


int main()

{

    Person p;

    Student s;


    eat(p); //正确

    eat(s); //正确

    study(s); //正确

    study(p); //错误


    return 0;

}

 

  • 上面的规则只对public继承才成立。private、protected不成立

二、设计正确的继承模型

  • 鸟可以飞,企鹅也是一种鸟。于是我们可能设计下面错误的继承模型:
    • 企鹅虽然属于鸟类,但是企鹅不会飞
    • 设计中,我们错误的将鸟类中的fly()虚函数派生给了Penguin类
//鸟类

class Bird {

public:

    virtual void fly();

};


//企鹅,也继承了fly()虚函数

class Penguin :public Bird {};
  • 我们应该修改上面的代码,下面才是合适的模型:
//鸟类

class Bird {

    //无fly()函数

};


//会飞的鸟类

class FlyingBird :public Bird {

public:

    virtual void fly();

};


//企鹅不会飞

class Penguin :public Bird {


};

三、以“编译期”确认关系代替“运行期”确认关系

  • 紧接着上面鸟类与企鹅的问题
  • 企鹅不会飞,但是我们仍然让Bird定义fly()函数,然后让Penguin继承于Bird,与上面不同的是,我们让Penguin在执行fly()函数的时候报出一个错误(运行期执行)代码如下:
class Bird {

public:

    virtual void fly();

};


void error(const std::string& msg);

class Penguin :public Bird {

public:

    virtual void fly() {

        error("Attempt to make a penguin fly!");

    }

};
  • 上面的代码是在运行期检查这种错误的
  • 下面我们设计让编译器在编译的时候检查出企鹅不会飞这种错误。代码如下:
class Bird {

    //无fly()函数

};



class Penguin :public Bird {

    //...

};


Penguin p;

p.fly();
  • 总结:
    • 上面我们介绍了检测“企鹅不会飞”这种错误的两种方式。一种为在运行期检测,一种为在编译期检测
    • 当然,我们希望在编译期的时候就确定企鹅不会飞这种关系,因此更希望以第二种方式(编译期)代替第一种方式(运行期)来设计继承关系

四、is-a模型的一些例外

考虑这样一个演示案例

  • 我们让正方形类(Square)public继承于矩形类(Rectangle)。如下所示:

  • 矩形类的代码定义如下:
class Rectangle {

public:

    virtual void setHeight(int newHeight); //设置高

    virtual void setWidth(int newWidth); //设置宽


    virtual void height()const; //返回高

    virtual void width()const; //返回宽

};
  • 现在有下面这个函数,下面函数中的assert永远为真,因为函数只改变了宽度,而没有改变高度:
//这个函数用来增加r的面积

void makeBigger(Rectangle& r)

{

    int oldHeight = r.height(); //取得旧高度


    r.setWidth(r.width() + 10); //设置新宽度


    assert(r.height() == oldHeight); //判断高度是否改变
    
}
  • 正方形类的代码定义如下:
class Square :public Rectangle {

    //...

};
  • 现在有下面的代码:
Square s; //正方形类


//...


assert(s.width() == s.height()); //永远为真,因为正方形的宽和高相同

makeBigger(s); //由于继承,我们可以增加正方形的面积


//...

assert(s.width() == s.height()); //对所有正方形来说,应该还是为真
  • 现在考虑上面的代码:
    • 第一步,我们判断正方形的宽和高,根据原理,assert应该返回真
    • 第二步,调用makeBigger()函数,这个函数改变了宽度,而没有改变高度
    • 第三步,再次调用assert应该还是返回真,因为此处的s为正方形
  • 现在我们可以看到:
    • 前面我们虽然提到过,作用于基类的代码,使用派生类也可以执行
    • 但是此处我们可以看到,某些施行于矩形类中的代码(例如只改变宽度而不改变高度),在正方形中却不可以实施(因为正方形的宽度和高度应该保持一致)
    • is-a并非是唯一存在于classes之间的关系。另两个常见的关系是has-a(有一个)和is-implemented-terms-of(根据某物实现出)。这些关系将在条款38和39介绍。在这些相互关系的塑造为is-a会造成错误设计

五、总结

  • “public继承”意味着is-a。适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值