• 《Effective C++》学习笔记——区分接口继承和实现继承


    派生类public继承自基类,其中函数均是接口继承。

    实现继承又分为缺省继承与强制继承,对应着虚函数与非虚函数。

    我们在做项目时,对于任何事物都要抱有先描述再组织的心态。

    因此,当描述事物为其构建框架尤其是存在继承(is-a)关系时,一定要搞清楚派生类与父类中函数关系。

    基类函数类型派生类继承方式
    纯虚函数(pure virtual)接口继承
    普通虚函数(impure virtual)接口继承+缺省实现继承
    非虚函数(non-virtual)接口继承+强制实现继承

    实现基类函数时,我们需要考虑基类该函数与派生类该函数关系后,再给予具体的实现方式。

    什么意思?

    对于无需实例化的基类,完全可以将其声明为抽象类,其中声明的纯虚(pure virtual)函数可以只给出声明。

    但如果所有派生类中该函数实现相同时,那可以将基类该函数声明为普通虚函数,这样当派生类定义时,可以直接调用基类函数,也就是缺省实现继承。

    但这样也有可能引发意想不到的意外:

    以书中例子做演示:

    我们先定义一个基类飞机,它会实现一个飞行方法,因为所有的飞机都能飞。

    1. class Airplane
    2. {
    3. public:
    4. virtual void fly()
    5. {
    6. ...
    7. }
    8. };

    之后派生出了各种不同型号的飞机...

    1. class ModelA : public Airplane
    2. {
    3. ...//没有重定义fly,会缺省继承自基类Airplane
    4. };
    5. class ModelB : public Airplane
    6. {
    7. ...
    8. };

    两个派生类共用一个fly函数,因为它们性质相同(都能飞),即体现了相同特性还避免了代码复用,看起来很完美不是?

    但此时如果需要新上线一款飞机,它与前两种的不同就在于飞行方式,又因为时间原因急于上架而忘记了对fly函数重定义,那灾难就发生了。

    1. class ModelC : public Airplane
    2. {
    3. ... //未重定义fly函数
    4. };

    当我们调用ModelC的飞行方法时,它会按A和B的方式进行,这显然大错特错

    1. ModelC c;
    2. c.fly();//按A和B的方式飞行???

    所以我们不妨将fly只提供一个接口给派生类,即只提供接口继承。这样当实现派生类时就会强迫它实现自己的fly版本。

    1. class Airplane
    2. {
    3. public:
    4. virtual void fly() = 0;
    5. };
    6. class ModelC : public Airplane
    7. {
    8. public:
    9. void fly()//强迫实现,否则不能实例化
    10. {
    11. ...
    12. }
    13. };

    对于A和B而言,想体现相同特性又要避免重复,那么可以在基类中定义一个函数,供A和B显性调用即可

    1. class Airplane
    2. {
    3. public:
    4. virtual void fly() = 0;
    5. protected:
    6. void defaultFly()
    7. {
    8. ... //实现一个飞行方式
    9. }
    10. };
    11. class ModelA : public Airplane
    12. {
    13. public:
    14. void fly() { defaultFly(); }
    15. ...
    16. };
    17. class ModelB : public Airplane
    18. {
    19. public:
    20. void fly() { defaultFly(); }
    21. ...
    22. };

    当然,这种方法也不是万全之策,因为它要求defaultFly函数应该是非虚(non-virtual)函数,任何一个派生类都不应该重定义它。

    但假如一时糊涂不小心定义成了虚函数,那么对于像C那样的派生类就需要重定义defaultFly函数,但假如忘了呢?

    对此我们可以有更好的方法:

    将fly声明为纯虚函数并给出实现方法。派生类如果需要使用基类fly方法时可以通过作用域手动调用,也可以重写一份不一样的飞行方式。

    1. class Airplane
    2. {
    3. public:
    4. virtual void fly() = 0
    5. {
    6. ... //实现一份共同的飞行方式
    7. }
    8. };
    9. class ModelA : public Airplane
    10. {
    11. public:
    12. void fly()
    13. { Airplane::fly(); }
    14. };
    15. class ModelB : public Airplane
    16. {
    17. public:
    18. void fly()
    19. { Airplane::fly(); }
    20. };
    21. class ModelC : public Airplane
    22. {
    23. public:
    24. void fly()
    25. {
    26. ... //重写一份不同的飞行方式
    27. }
    28. };

    go big or go home——facebook


    如有错误,敬请斧正 

  • 相关阅读:
    IDEA集成GitHub
    Ubuntu下解压文件(提取文件总是报错)文件是zip 格式
    谷粒商城--环境部署(2022/7/28最新)
    前端架构思考,Vue or React?领域设计、文件结构、数据管理、主题替换
    dubbo 服务注册使用了内网IP,而服务调用需要使用公网IP进行调用
    Docker 容器文件(数据)共享
    SpringBoot3整合RabbitMQ之一_消息生产者与消息消费者服务搭建
    C++标准模板(STL)- 类型支持 (类型特性,)
    苹果首破例,允许在韩使用替代支付系统
    为什么要分库分表?(荣耀典藏版)
  • 原文地址:https://blog.csdn.net/weixin_61857742/article/details/127616307