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

一. 内容

  1. 以 C++ 进行面向对象编程,最重要的一个规则是:public inheritance (公开继承)意味着 is-a 的关系,请把这个规则牢牢记在心中。

  2. 也就是说,如果你令 class D 以 public 的方式继承 class B,你便是告诉 C++ 编译器和你的客户:每一个类型为 D 的对象,同时也是一个类型为 B 的对象,反之则不成立。类型 B 比 类型 D 表现出更一般的概念,而类型 D 比类型 B 表现出更特殊化的概念。B 对象可使用到的地方,D 对象都可以派上用场反之,如果你需要对象 D ,B 对象并不能效劳,因为 B 对象并不具备 D 对象所含有的特殊化信息

  3. 以上论述只对 public 继承才成立,private 继承的意义与此完全不同,见条款39,至于 protected 继承,那是一种意义至今令我疑惑的东西。

  4. 虽然 public 继承意味着 is-a 的关系看似简单,但有时候你的直觉会误导你。举个例子:企鹅是一种鸟,这是事实。鸟可以飞,这也是事实。如果我们写下以下代码:

    class Bird {
    public:
        virtual ~Bird();
        virtual void Fly(); //鸟可以飞
    };
    
    class Penguin : public Bird { 	    //企鹅是一种鸟
    public:
        //...
    };
    

    在这个例子中,我们的实现和所说的事实存在着误差,事实上我们说鸟可以飞,不是说所有的鸟可以飞,如果严谨一些,有一些鸟是不会飞的。所以修改如下:

    class Bird { //没有声明 fly 函数
    public:
        virtual ~Bird();
    };
    
    class FlyingBird : public Bird { //会飞的鸟
    public:
        virtual void Fly(); 
    };
    
    class Penguin : public Bird {  
    public:
        //...
    };
    

    即便如此,我们可能仍未处理好这些鸟事,因为对于某些系统而言,可能根本不需要区分会飞的鸟和不会飞的鸟。这反映出一个的问题:世界上并不存在一个适用于所有软件的完美设计。所谓最佳设计,取决于系统希望去做什么事,包括现在和未来

  5. 处理鸟事的另一种流派是:为企鹅重新定义 fly 函数,令它产生一个运行期错误:

    class Bird {
        //没有声明 fly 函数
    public:
        virtual ~Bird();
        virtual void Fly();
    };
    
    class Penguin : public Bird {
    public:
        virtual void Fly() override; //抛出错误
    };
    
    inline void Penguin::Fly() {
        std::cout << "企鹅不会飞,你在想什么?" << "\n";
    }
    

    然而这种做法其实和原本你的想法不同:这里不是说 企鹅不会飞,而是说 企鹅会飞,但尝试那样做 是一种错误。如何描述两者的差异?可以从发现错误的时机来看,企鹅不会飞可以在编译期检测出来,但若违反企鹅尝试飞行,是一种错误,只有在运行期才能侦测出来

    为了在编译器抛出错误,你不可以为 Penguin 定义 fly 函数:

    class Bird {  //没有声明 fly 函数
    public:
        virtual ~Bird();
    };
    	
    class Penguin : public Bird { 	   //没有声明 fly 函数
    public:
        //...
    };
    
    inline void Try(){
    	Penguin P;
    	P.fly();  //编译器报错!
    }
    

    这种做法和令程序在运行期发生错误极为不同,以后者做法,编译器不会对 P.fly() 的调用发出任何抱怨。条款18说过,好的接口可以防止无效的代码通过编译,因此你应该宁可在编译期拒绝企鹅飞行,而不是在运行期侦测企鹅飞行的错误

  6. 另一个有趣的例子是:矩形和正方形的例子。数学知识告诉我们:正方形是一种矩形,你会很容易的想到让 正方形 public 继承 矩形,毕竟这是 is-a 的关系。但事实并非如此,例如 矩形的长宽可不一致,可以独立修改,按照 public 继承的观点,作用于 base class 的事情, derived class 应同样适用。但正方形的长宽应始终保持一致。所以它们之间的关系用 public 继承并不合适。虽然代码上编译器不会有任何报错,但它们的行为是错误的。这也让我们明白一个道理:代码通过编译并不意味着可以正确运作。

二. 总结

  1. 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、付费专栏及课程。

余额充值