Effective C++ 阅读笔记之Chapter6

前言

大家好,我是雨墨,我深知好记性不如烂笔头的道理,所以在阅读的时候都尽量写读书笔记,方便日后复习,当然笔记并不能代替书籍上的内容。希望我的笔记也能帮助到大家,如果我的笔记有什么问题,欢迎大家给小老弟纠错~

条款三十二总结

public 继承就是 is-a !!! 适用于 base class 身上的每件事情也一定适用于 derived class ,因此在设计 public 继承的时候一定要考虑这一点,多想想企鹅和鸟、矩形和正方形之间的关系。

条款三十三总结

注意:在 public 继承下掩盖名称违背了 “public 继承就是 is-a” ,因此不要这样做,如果真的这样做了,即覆盖了名称,使用 using 声明式,会令继承而来的某给定名称的所有同名函数在 derived class 中都可见。

class Base {
public:
    virtual void mf1();
    virtual void mf1(int);
    virtual void mf2();
    void mf3();
    void mf3(double);
    ...
};

class Derived : public Base {
public:
    using Base::mf1;
    using Base::mf3;
    virtual mf1();
    void mf3();
    void mf4();
    ...
};

/*********示例*******/
Derived d;
int x;
...
d.mf1();	// 正确,使用的是 Derived::mf1()
d.mf1(x);	// 正确,使用的是 Base::mf1(int)
d.mf2();	// 正确:使用的是 Base::mf2()
d.mf3();	// 正确,使用的是 Derived::mf3(),但这样写不好
d.mf3(x);	// 正确,使用的是 Base::mf3(double)

如果你不想继承 Base 中的所有函数,在 public 继承是不被允许的,可以考虑 private 继承。此时你就不能使用 using 声明式了。而实使用一个简单的转交函数。

class Base {
public:
    virtual void mf1();
    virtual void mf1(int);
    ....
};

class Derived : private Base {
public:
    virtual void mf1() {
        Base::mf1();	// 实则调用的是 Base::mf1()
    }
    ....
};

/*************示例*************/
...
Derived d;
int x;
d.mf1();	// 正确
d.mf1(x);	// 错误,Base::mf1(int) 被遮掩了

条款三十四总结

条款名称为区分接口继承和实现继承,应该从以下几个方面去理解它:

  • 书上有一句话:函数接口继承和函数实现继承的差异就像函数声明和定义之间的差异,书中的例子也是围绕这里展开的。
  • 当你声明一个 pure virtual 函数的时候,你想做的是继承其接口,然后自己去实现,这就好像是只声明而不下定义。
  • 当你声明一个 impure virtual 函数的时候,你想做的是继承其接口和缺省实现,在有必要的时候自己去实现。
  • 当你声明一个 non virtual 函数的时候,你想做的是,别动它!就让它一直这样。

书中的例子我觉得很有意思,看本条款的时候应该回顾一下书上内容。

条款三十五总结

当你为解决问题而寻找某个设计方法的时候,不妨考虑 virtual 的替换方案:

这里只是提出几种可行方案,具体实现例子见书 p169-p177

  • NVI 手法,将 virtual func 放入 private 部分,然后再使用 public non-virtual 函数调用之,derived class 可以改写 virtual func 。优点是你可以在 non-virtual func 中 “做一些事前工作” 和 “做一些事后工作” 。
  • Strategy 设计模式
    • 由 Function Pointers 实现
    • 由 function 实现
    • 由 函数对象实现
    • 由继承体系中的 virtual func 替换另一继承体系中的 virtual func 实现。

别看它短,其实很复杂,设计两种设计模式,遇到时仔仔细细阅读书!

条款三十六总结

坚决抵制重新定义继承而来的 non-virtual func !还记得前面 Derived::non-virtual func 覆盖 Base::non-virtual func 的例子吗?这会严重破坏 public 继承是 is-a 的特性!除非你不想用 public 继承。

条款三十七总结

条款名称为:绝不重新定义继承而来的缺省值参数值,其原因是:对于 virtual 函数是静态绑定,但对于缺省参数值却是动态绑定,如果擅自修改继承而来的缺省参数值,会造成:

class Base {
public:
    enum ShapeColor {Red, Green, Blue};
	virtual void draw(ShapeColoe color = Red) const = 0;
    ...
};
class Derived {
public:
	virtual void draw(ShapeColoe color = Green) const = 0;
    ...
};

/************造成结果****************/
Base* ps = new Base;
Base* pc = new Derived;
ps->draw();	// 调用的缺省实参为 Red 的 draw
pc->draw();	// 我想调用的是缺省实参为 Green 的 draw ,可实际调用的是 缺省实参是 Red 的 draw,让我百思不得其解啊...

解决方案:考虑 virtual func 的替换方案,这不就用上了前面学习的知识了吗。

// NVI
class Base {
public:
    enum ShapeColor {Red, Green, Blue};
    void draw(ShapeColor color = Red) const {
        doDraw(color);
    }
    ...
private:
    virtual void doDraw(ShapeColor color) const = 0;
    ...
};
class Derived {
public:
    void draw(ShapeColor color = Green) const {
        doDraw(color);
    }
    ...
private:
    virtual void doDraw(ShapeColor color) const;
    ...
};

条款三十八总结

复合(composition)意味着 has-a 的关系,例如:

class Adress {.....};
class PhoneNumber {....};

class Person {
public:
    ...
private:
    string name;
    // 一下是复合内容,表示 Person has address、phoneNumber
    Address address;
    PhoneNumber voiceNumber;
    PhoneNumver faxNumber;
};

如果你很想通过 reuse 来创造你的类,但是无法通过继承,因为那可能会破坏 is-a 的关系,那么 composition 可能是你不错的选择。

条款三十九总结

首先我要告诉你 private 继承是 has-a 的关系,那肯定有人会问,上一个条款所讲的复合不也是 has-a 的关系吗?那我该如何选择呢?

作者是这样说的:

  1. 当一个意欲为 derived class 者想访问一个意欲为 base class 者的 protected 成分的时候,或是为了重新定义一个或多个虚函数的时候(这里书上有明确的例子,还记得 Widget 以及 Timer 吗?),应该使用 private 继承。
  2. 如果你想追求 EBO ,这种情况下很少见,你也应该声明成 private 继承,稍后会给出 EBO 的解释。

何为 EBO ?比较新颖hhh

EBO 就是当你声明一个空类,而又在另一个类中使用它,如果你将使用它的类定义为 private 继承空类,那么编译器就不会计算空类的大小,否则,编译器至少给空类安插一个 char 大小,有时因为对齐可能造成空类能存放更大的大小。

private 继承造成的结果是什么?

  1. 如果 classes 之间的继承关系是 private 继承,编译器是不会自动将 derived class 对象转化为 Base class 对象,因为这不是 is-a 关系!
  2. 在 C++ Primer 中有提到,当一个 derived class 对象以 private 继承自 Base class 对象,Base class 对象的 public 以及 protected 成分可以被 derived class 对象使用,但是继承自 derived class 对象的子对象则不能使用 Base class 对象的以上两种对象。因此书上讲有 private base class 继承而来的所有成员,在 derived class 中都会变成 private 属性,两本书就这样知识相同了~

因此 private 继承纯粹就是一种实现技术,也就是说,继承而来的成分都是 derived class 的实现枝节罢了。

另外,private 继承可以通过复合来代替之。具体实例书上 p189 有讲解。

下面给出为什么说尽量不用 private 继承?

  1. 如果你是使用 private 继承 base class,而你又想阻止你的 derived class 重新定义你的虚函数,这几乎是不可能的,除非你使用 final 阻止继承,因为子类即使不能使用虚函数,但也可以重新定义它!
  2. 如何你使用 private 继承,那你就必须在你的类被编译之前给出基类的定义,这会存在编译依存性。

综上:除非你觉得非得使用 private 继承不可,否则还是用复合吧~

题外话

我在极客时间罗剑锋的C++实战笔记中学习怎样才能写出一个好的 “类” ,现分享给大家:

  • 面向对象的出发点在于从 “对现实世界的模拟” 出发,将实际问题的实体抽象出来,封装成类和对象,建立一个 “虚拟模型” ,是不是有点类似于建模,再以这个虚拟模型为基础,不断地扩展,抽象对象直接的关系和通信,最后造就一个许多对象相互联系的系统,这就是面向对象编程了。
  • 面向对象的设计思想是抽象封装,多态和继承并不是核心,只能算是附加品。
  • 继承反而是面向对象的缺陷,原本使用继承是想复用代码,但会造成一些与事实不符的情况,比如本书中的 “企鹅会不会飞?” 以及 “正方形是不是矩形?” ,为了解决继承的问题,C++ 引入了虚函数、多态、重载,但也使得代码变得复杂。
  • 建议少使用虚函数和继承,保留纯正的类,减少类之间的耦合关系,使得类更独立。
  • 如果需要继承,控制继承的层次,尽量不要让继承深度超过三层。做法:在最底层的时候善用 final 切断继承关系。
  • 在类内部定义一些嵌套类,也是一种不好的习惯,因为这些内部类与上级类形成了强耦合关系,应该将嵌套类拿出去,放到同一个命名空间中。
  • 如果需要继承,建议只用 public 继承,前面条款也讨论了不到万不得已不用 private 继承,同时避免使用 protected、virtual 继承。当继承关系到底层的时候,及时使用 final 。
  • 对于一些较重要的 ctor 、dtor ,如果不想写其定义,使用 =default ,显式告诉编译器。同时当你不想使用某个函数时,显式使用 =delete。
  • 如果你想要防止隐式转换,显式使用 explicit 。
  • 当你 ctor 中有大量重复代码时,多用委托构造。
  • 善用类型别名,这样可以增加代码的可读性。
  • 成员变量有很多的时候,如果都交由构造函数初始值列表进行默认初始化会造成不美观,同时可能少写某个成员造成未初始化,这时候你可以在类声明变量的时候实现初始化。

参考书籍及文章:

  • 《Effective C++》
  • 极客时间罗剑锋C++实战笔记专栏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值