6. 继承与面向对象设计

C++的OOP有可能是单一继承或多重继承,每一个继承连接(link)可以是 public,protected 或 private,也可以是 virtual 或non-virtual。成员函数的各个选项:virtual?non-virtual?pure virtual?以及成员函数和其他语言特性的交互影响:缺省参数值与virtual函数有什么交互影响?继承如何影响的名称查找规则?设计选项有哪些?如果class的行为需要修改,virtual函数是最佳选择吗?

32. 确定你的public继承塑模出is-a关系

  1. Derived是Base,所以能用Base的地方,就能使用Derived替换,无论是对象、指针还是引用
  2. 世界上并不存在适用于所有软件的"完美设计",最佳设计取决于系统希望做什么,包括现在和未来
  3. is-a:适用于base身上的每一件事情也一定适用于derived身上,即Derived总是继承base的所有接口
  4. 有的时候可能在cpp世界中与常识相反:正方形square和rectangle之间,其实并不是is-a关系,因为rectangle内部可以随意修改长宽,但是square确对长宽修改有要求

33. 避免遮掩继承而来的名称

  1. 当derived中有与base同名方法,则会直接隐藏掉,无论是否参数、返回值不同,甚至也无关virtual(所以多态可能导致隐藏),只与名称相关;这么做的原因是:防止在程序库或框架中建立新的Derived时附带地从疏远的base classes继承重载函数。
  2. 隐藏之后就类似于没有继承这些同名函数,但是这与public继承所有接口,可借助using 来解决,如using Base::f1;
  3. 当使用private继承时,有时候只是需要Base中某名称函数的部分,而不是全部(故using不满足),可以借助转交函数 (inline函数),如void f1(){Base::f1();}//这里类内部实现,隐藏inline,只暴露了Base::f1的无参版本

34. 区分接口继承和实现继承

  1. public继承之下,Derived总是继承base的所有接口
  2. pure virtual 目的是为了让Derived只继承函数接口,但是其实是可以给pure virtual函数提供一一份实现的,然后使用类名::函数名来调用
  3. impure virtual目的是让derived继承函数接口以及缺省实现,但正是这缺省实现,可能让derived忘记重新定义,进而导致出错,推荐使用pure virtual,然后提供实现,再在内部调用,类似于转交函数
  4. non-virtual目的是为了让derived继承函数接口以及一份强制性的实现,绝不应该在derived中被重新定义

35. 考虑virtual函数以外的其他选择

  1. 其实本章我也不知道为啥要用其他方式来替换virtual,希望以后能顿悟吧
  2. 具体方法参见链接考虑virtual函数以外的其他选择

36. 绝不重新定义继承而来的non-virtual函数

  1. 如果重新定义了,那会导致调用该函数会因为指针的类型(base or derived)不同而不同
  2. 谨记non-virtual函数是为该class建立起一个不变性

37. 绝不重新定义继承而来的缺省参数值

  1. 由条框36可知,重新定义的只能是virtual函数,故此条款针对的virtual函数
  2. 缺省参数值,其实是静态类型,编译器根据指针类型,去绑定指定值,而virtual却是为了动态,两者其实天生矛盾
  3. 本条应该是说:永远不要在虚函数中定义默认参数
  4. 如果希望使用base类提供的默认值,可使用NVI(public non-virtual 调用private virtual函数,派生类重定义virtual函数即可,外部调用non-virtual函数)
cpp 复制代码
class Base {
public:
    void foo(int x = 10) {  // 非虚,统一管理默认值
        doFoo(x);
    }
private:
    virtual void doFoo(int x) { /* 基类实现 */ }
};

class Derived : public Base {
private:
    void doFoo(int x) override { /* 派生类实现 */ }
};

38。 通过复合模塑出has-a或"根据某物实现出"

  1. 复合,某种类型的对象含有其他类型的对象
  2. 应用域,对象相对于所塑造世界中的某些事物,比如人等
  3. 实现域,实现细节上的人工制品,如缓冲区、互斥器、查找树等
  4. 当复合发生在应用域就是has-a关系(如人有地址电话),实现域就是"根据某物实现出"的关系(如根据queue实现出stack)

39. 明智而审慎地使用private继承

  1. 如果是private继承,编译器不会自动将derived class转换为base class对象;private继承而来的所有成员都会变为private属性
  2. private继承意味"由某物实现出"
  3. private继承意味着只有实现部分被继承,接口部分应略去
  4. 尽可能使用复用,只有当protected成员或virtual函数牵扯进来时,才使用private继承
  5. private还可能有empty base最优化,能节省内存

40. 明智而审慎地使用多重继承

  1. 多重继承可能引入歧义性、菱形继承的问题
  2. virtual继承会大小、速度等成本,且virtual base的初始化责任是由继承体系中最底层class负责,故尽量不要在virtual base中有数据
  3. 当涉及"public继承某个interface class"和"private继承某个协助实现的class"的两相组合,则不得不使用多重继承
相关推荐
胖咕噜的稞达鸭1 小时前
基础IO 文件在内核中是怎么被管理的 重定向的含义 在自定义shell中加入重定向
linux·c++·git·腾讯云·visual studio·csdn开发云
枫叶丹41 小时前
【Qt开发】Qt窗口(四) -> QDockWidget浮动窗口
c语言·开发语言·c++·qt·开源
乌萨奇也要立志学C++1 小时前
【洛谷】二分答案专题 3 道洛谷经典题(木材 / 砍树 / 跳石头)精讲
c++·算法
de_furina1 小时前
[C++]string类的使用和模拟实现
开发语言·c++·gitee
LaoZhangGong1231 小时前
“do{}while(0)”的作用
c++·mfc
Elnaij1 小时前
从C++开始的编程生活(14)——容器适配器——stack和queue
开发语言·c++
博语小屋2 小时前
Linux线程
linux·c++·spring
丁劲犇2 小时前
MSYS2下使用libbacktrace为MINGW编译器Release模式导出崩溃堆栈
c++·msys2·mingw64·backtrace·crashdump·崩溃堆栈·跟踪堆栈
FMRbpm2 小时前
链表实现栈:具体函数实现
数据结构·c++·新手入门