Effective C++条款39:继承与面向对象——明智而审慎地使用private继承

本文详细介绍了C++中private继承的概念,包括其语法特点和含义,指出private继承主要用于实现细节,而不是公开接口。通过案例分析了private继承与复合模式在实现特定功能时的差异,强调了复合模式在防止派生类误用和减少编译依赖的优势。此外,还提到了private继承在空基类优化(EBO)中的作用,以及何时考虑使用private继承。
摘要由CSDN通过智能技术生成

一、先介绍一下private继承的语法

  • 某派生类private继承于基类之后:
    • ①基类中的所有内容(不论是public、protected、private)在派生类中都是不可访问的
    • ②不能再将派生类对象转换为基类对象
    • ③派生类仍然可以重写/隐藏基类的成员方法

二、private继承意味着什么关系?

  • private继承意为implemented-terms-of(根据某物实现出):
    • 假设你让class D以private继承于class B,用意为采用class B内的某些特性来实现class D,再无其他意义了
    • 借助条款34的属于:private继承意味只有实现部分(也就是基类中已经实现的函数)被继承,接口部分(基类中只定义还未实现的)应该被省去
  • 与类的复合模式的关系:
    • 在条款38中介绍了类的复合(composition)模式,其中类的复合也有着“is-implemented-in-terms-of”的意义
    • 两者有着同样的意义,但是建议:尽可能使用复合,必要时才使用private继承
    • 何时才必要使用private呢?
      • 主要是当protected成员和/或virtual函数牵扯进来的时候。当派生类想要访问基类的protected成分或者派生类想要重写一个或多个virtual函数
      • 还有一种情况,就是当空间方面的利害关系足以踢翻private继承的支柱时(下面介绍)

三、演示案例

  • 假设我们有这样一个需求:
    • 我们有一个类Widget,现在想要知道Widget成员函数被调用的次数
    • 现在我们绝对修改Widget class,让它记录每个成员函数被调用次数
    • 为了完成这项工作,我们定义了下面的定时器,该定时器每隔一段时间就会对数据进行统计(统计每个成员函数被调用的次数)
class Timer {

public:

    explicit Timer(int tickFrequency);

    virtual void onTick()const; //定时器每滴答一次,此函数就被自动调用一次

};
  • 现在我们需要:
    • Timer类会根据设置的频率进行滴答前进,每次滴答就调用virtual函数
    • 现在我们需要Widget类继承于Timer,然后重新定义这个virtual函数,然后使用该函数来统计Widget的数据(统计每个成员函数被调用的次数)

①错误的做法:以public方式继承

  • 错误的做法是让Widget以public方式继承于Timer,然后重写其virtual函数
class Timer {

public:

    explicit Timer(int tickFrequency);

    virtual void onTick()const; //定时器每滴答一次,此函数就被自动调用一次

};


class Widget :public Timer {

public:

    virtual void onTick()const;

};
  • 为什么错误:
    • 类似于is-a的关系:我们知道Widget不是一个Timer,我们总不该对一个Widget调用onTick()函数吧?这样会显示很诡异
    • 另外,会违反条款18的忠告:让接口容易被正确使用,不易被误用

②以private方式继承

  • 为了完成上面的需求,我们可以让Widget以private的方式继承于Timer
  • 代码如下:
class Timer {

public:

    explicit Timer(int tickFrequency);

    virtual void onTick()const; //定时器每滴答一次,此函数就被自动调用一次

};


class Widget :private Timer {

private:

    virtual void onTick()const; //查看Widget的数据..等等

};
  • 我们在Wiget中重写了onTick()函数,但是将其声明为private的,不能将其声明为public接口,因为如果声明为public,又再次与上面的public继承相类似了

③以复合的形式实现

  • 在条款38我们介绍过,复合形式的类也有着“is-implemented-in-terms-of”的意义,因此我们还可以使用复合模式来实现这个功能
  • 代码如下:
class Timer {

public:

    explicit Timer(int tickFrequency);

    virtual void onTick()const; //定时器每滴答一次,此函数就被自动调用一次

};


class Widget{

public:

private:

    class WidgetTimer :public Timer {

        public:

            virtual void onTick()const;

    };

    WidgetTimer timer;

};

  • 我们派生了一个Timer的派生类WidgetTimer,并重写onTick()函数,然后定义一个WidgetTimer类对象定义于Widget class中
  • 相同的问题,建议使用复合模式,而不建议使用private继承,原因有两点:
    • ①防止Widget的派生类重写onTick()函数:
      • 在继承方式下:如果Wiget又定义了派生类,你不希望派生类去重写onTick()函数,但是这种情况可能会无法阻止
      • 在复合模式下:Widget的派生类就不可能有机会去重写onTick()函数了,因为WidgetTimer类是Widget内部的一个private成员,派生类永远无法访问
    • ②可以将Widget的编译依存性降至最低:
      • 在继承方式下:如果Widget继承与Timer,那么当Widget被编译时需要知道Timer的定义(不仅仅是声明),因此你可能会在Widget的头文件中包含#include "Timer.h"这样的东西
      • 在复合模式下:假设我们修改上面的复合模式,将WidgetTimer定义在Widget之外,然后在Widget内定义一个WidgetTimer的指针,此时Widget可以只带着WidgetTimer的声明式,那么当Widget编译时就不需要任何与Timer的任何东西。对大型系统而言,这是很重要的措施

四、private继承的另外一个使用场景

  • private继承还用于:基类的class不带有任何数据时
  • 基类通常:没有non-static成员变量,也没有virutal函数(因为这种函数的存在会为每一个对象带来一个vptr,见条款7),也没有virtual base classes(因为这样的base classes也会导致类的体积增大,见条款40)

演示案例

  • 当一个类没有任何成员变量时,编译器会自动为其设定大小(不同的编译器不同),通常为1字节(C++默认安插一个char到空类中)。例如:
class Empty {}; //空类


sizeof(Empty); //1字节
  • 上面的规则是适用于独立的类,如果其有派生类,并且派生类有成员,那么这种规则就会消失。例如:
class Empty {};


class HoldsAnint :private Empty {

private:

    int x;

};


sizeof(HoldsAnint); //4
  • 这个规则就是所谓的EBO:空白积累最优化(empty base optimization)。如果你的程序非常在意空间,那么值得注意EBO
  • 另外还需要注意:EBO一般只在单一继承(而非多重继承下)才可行
  • 例如STL有很多实现中就用到了empty classes。例如unary_func和binary_function等。EBO使继承很少增加派生类的大小

五、总结

  • private继承意为“is-implemented-in-terms-of(根据某物实现出)”。它通常比复合(composition)的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计时合理的
  • 和复合(composition)不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值