《Effective C++》-读书笔记

2016/04/05 菜鸟日记

背景

前段时间看完了《C++ Primer》,看这本书有种看字典的感觉。 《C++ Primer》一书内容全面,但是十分琐碎,一时间让人抓不住重点,我也是不求甚解地去看。 然后就来看这本升级版的《Effective C++》,希望借助实战经验,来巩固C++语法知识。

这是第二遍看《Effective C++》了!距离上一次看有一个月了,发现自己该忘的不该忘的都基本忘干净了,好可怕~ 所以呢~就写这篇笔记。(~_~;)

总述

这本书结合作者多年的C++编程经验,通过9个章节,55个建议,介绍了在编程时应该遵循的一些准则, 以及提高程序运行效率的一些细节。作为一个小白,我大部分都不知道。这本书对我编程习惯的影响仅次于《数据结构》。 下面以每章为逻辑,简单介绍:

正文

第1章:让自己习惯C++

1:为了让自己习惯C++。 Scott Meyers首先把C++语言分为了4大部分:C、OOC、Template C++以及STL标准模版库。

2:使用编译器代替预处理器(使用const,enum,inline 代替define) const与define相比有更省内存,方便错误检查,以及作用域控制等优点; enum可以用于保存常量值,且不会引起多余的内存消耗; inline 相比函数宏 具有类型检查的优点。

3:尽可能多的使用const。 使用const能进行安全检查!

4:确保对象使用前被初始化。 内置类型需要手动初始化,STL中自动初始化;对象的copy会先调用初始化,引起不必要的操作, 可以使用成员列表赋值;使用成员static代替普通static,通过函数调用的方式自动初始化,返回&static 。

第2章:构造、析构、赋值运算

5:C++默认编写的成员函数:构造函数,析构函数,复制构造函数,赋值操作运算符。 复制(构造/运算符)函数生成有一定的条件!

6:如果不适用默认函数,就应该明确拒绝。 方法一:只写声明,不定义; 方法二:基类将相应函数定义为私有。

7:为多态基类生命virtual析构函数。 为至少含有一个virtual的函数定义虚析构函数,虚析构函数也不要乱用。

8:别让异常逃离析构函数。 使用std::abort,吞下异常,自我管理等方式,处理析构函数调用时可能发生的异常。

9:决不在构造、析构函数中调用virtual函数。 虚函数调用结果可能跟自己的预期不符!

10:令operator= 返回一个reference to * this。 返回一个左侧值的引用,方便使用a=b=c=5这种连续赋值。

11:在operator中处理“自我赋值”。 使用“认同测试” “调整语句顺序” “copy-and-swap”技术

12:复制对象时不要忘了每一个成分。 包括自身的数据,以及通过调用基类的复制构造函数实现基类元素的赋值。

第3章:资源管理

常见资源:内存、文件、互斥锁、画刷、数据库连接、Socket等

13:以对象管理资源。在构造函数中申请资源,在析构函数中释放资源。 使用shared_ptr智能指针,自动调用对象析构函数释放资源。

14:在资源管理类中小心coping行为。(没看太懂《~.~》)

15:在资源管理类中提供对原始资源的访问。 资源管理类提供资源访问方法:智能指针调用方法,或者显示get()调用,少用隐试转换。

16:成对使用new 和delete时要采用相同的形式。 new-delete new[]-delete[] ,尽量不要对数组使用typedef,尽量多使用vector

17:以独立语句将newed对象置入智能指针。 防止分开定义,在定义后,置入前发生异常,引起内存泄漏。

第4章:设计与声明

18:让接口容易被正确使用,不易被误用。 1:设计不易被误用的接口; 2:促进正确使用:接口一致,内置类型兼容; 3:阻止误用,通过类型限制,消除客户端管理; 4:share_ptr可定制删除器。

19:设计class犹如设计type。 定义类要思考的够全面!(为什么我感觉这章并没什么卵用?)

20:使用const & 代替按值传递。按址传递,减少了copy和切割。 提高效率。但是不适用于系统自定义类型。

21:函数返回对象实体,不能返回引用。 引用局部变量会异常,使用static变量可能会达不到预期的效果。

22:将成员变量声明为 private。 private是封装型的体现,有利于类的复用与修改。

23:使用non-member、non-friends代替member函数。 成员函数成员变量的访问降低了封装型。可以使用命名空间将功能分散在多个头文件中。 (不太认同,方法也是对象的属性呀,非成员函数多了,就没面向对象的感觉了。)

24:如果所有参数都需要类型转换,请使用非成员函数。 以operator中this指针强制转换为例。

25:尝试写出一个不抛出异常的swap函数。 1:定义成员swap, 2:类偏特化swap,使用Using来调用对应的版本。

第5章:实现

26:尽量延后变量定义式出现的时间。 延后到初始化的值出现。减少默认构造函数的使用, 在循环中,如果赋值成本小于构造+析构,定义在外部,否则定义在内部。(个人感觉会影响代码的可读性)

27:尽量少做转型动作。 转型分旧式和新式转型,const_cast,dynamic_cast,static_cast,reinterpret_cast。 可以使用一些替代方案,比如静态调用等。

28:避免返回handles指向对象的内部成分。 避免返回(引用、指针、迭代器)类型的数据指向对象内部,这样会破坏原有的封装型。

29:为“异常安全” 而努力是值得的。 异常安全函数包括(强烈保证、基本保证、不抛出)3中。 使用copy-and-swap即可实现强烈保证,但并不是所有情况下都必须。外层函数的安全性,等于器调用函数的最小值。

30:完全了解inlining里里外外。 inline函数能提高程序效率。但是大量使用inline会导致代码膨胀,降低效率,而且整加调试难度。 声明inline函数,并不一定为inline函数,其结果取决于编译器,如虚函数等。不建议将构造析构函数声明为inline函数。 定义在类内的函数,默认为inline函数。

31:将文件的编译依存关系将至最低。 (building,complie,link区别)(第二遍看完还是不太理解) 原则:依赖声明,不依赖定义!对应的技术分别为:handle class和interface class。 前者要求类内成员多使用指针或引用,类外多使用声明。但是会需要多保存一个指针变量,较多的使用动态内存。 后者要求使用纯虚基类,使用工厂函数生成子类!但会多消耗一个虚函数表!

第6章:继承与面向对象设计

32:public继承是一种is-a关系。 适用于base class的特性,一定适应于derived class。

33:避免覆盖继承而来的名称。 覆盖会覆盖同名函数,同名变量。与返回类型、参数类型无关。 解决覆盖方法: 1:使用using声明; 2:(主要用于1失效的情况)使用转交函数调用Base::fun();

34:区分接口继承和实现继承。 pure virtual:继承接口,纯虚函数也可有实现。 impure virtual: 继承接口和默认实现; non virtual:继承接口和强制实现。

35:考虑virtual函数以外的其他选择。 虚函数的4中替代方法: 1)使用non-virtual函数代替虚函数。 2)使用函数指针转移到类外部 3)使用tr1:function对象 4)(设计模式)将虚函数分离为另外一个继承体系。不知道什么设计模式,尴尬 >_<|||

36:绝不重新定义继承而来的非non-virtual函数。 non-virtual提供接口和强制实现继承。不要重新定义。

37:绝不重新定义继承而来的缺省参数值。 缺省参数是静态绑定,virtual函数是动态绑定。基类缺省参数会传到子类,但虚函数不会。 有效防止重复定义的方式是使用non-virtual函数替代虚函数。(其实没看太懂⊙﹏⊙)

38:类成员与类的has-a关系。 使用复合得出has-a关系

39:明智而审慎地使用private继承。 private继承的意义是“由某物实现出”,类似has-a的概念。多数情况下优先使用复合代替。但在以下2中情况必须使用private继承 1:子类要使用基类的protected或实现virual函数。2:空类(non-static成员类),private继承比复合更省空间。 (个人感觉,private继承使用的地方好少!)

40:明智而审慎地使用多继承。 大多数多继承都可以使用单继承来实现,推荐使用单继承,因为有更清晰的结构。 多继承可能会有歧义,使用虚继承,或作用域运算符来消除歧义。 多继承在public继承一个接口,private继承一个协助实现的类上有用! (感觉多继承使用的地方也很少 ^_^)

第7章:模版与泛型编程

41:隐式接口与和编译期多态。 class:显试接口,virtual函数提供运行时多态。 template:隐式接口,(提供的操作函数,使用类型转换降低对类型的要求),模版函数提供编译时多态。

42:typename的双重意义。 1:与class意义相同,用于类型说明。template ==template 2:用与从属名称的类型说明。 typename c::type 如果不声明,编译器默认认为嵌套从属名称不是类型。 但是不用再基类列和成员初始列前面。

43:处理模版化基类内的名称。 模版基类使用时会出现找不到基类函数的错误。 解决方法:1:使用this->指针2:使用using声明 3:使用基类作用域运算符(会破坏virtual的运行时多态)

44:将参数无关的代码抽离template. 过多的模版参数会产生较多的模版实例,引起代码膨胀。解决方法: 1:消除非类型模版参数,以函数参数或类成员代替模版参数。 2:消除类型模版参数,让具有相同二进制表述的具现类型共享代码。

45:运用成员函数模版接受所有兼容类型。 1:定义成员模板函数,自适应代码类型,减少代码量。创建成员函数组进行隐式类型转换筛选。 2:成员模板函数与普通函数不冲突,必要时需要都定义。

46:需要类型转换时请为模板定义非成员函数。 使用类模板时,包含模板成员函数,且该函数可以进行隐式类型转换, 那么需要将该函数声明为该类的friend函数,为了避免被inline使用该函数调用一个外部函数。

47:使用traits classes 表现类型信息。 使用扩从Class或Struct 增加辨别类型的新属性。通过typedef定义属性,通过typeid查找原属性,进行判断。 使用函数重载将运行期间的if提前到编译期。但是会整加代码的大小。

48:template元编程 TMP(模板元编程)可以将运行期间的工作提前到编译期,较早地检测到错误。(感觉:然并卵!)

第8章:定制new与delet

49:new-handler的行为。 1:new-handler是在new申请内存失败后自动调用的函数,可以通过set_new_handler(…)来安装,通过安装就函数来卸载。通常使用自定义类NewHandlerSupport来管理。 2:可以使用 Obj obj1=new (std::nothrow)Obj 来处理new时的失败并返回NULL。

50:new和delete的合理替换时机。 new和delete是类的一个运算符,可以自定义运算符!自定义new和delete在有些情况下可以使用: 如提高效率、降低申请大小、内纯齐位、次数统计等。

51:编写new和delete需要固守常规。 标准的operator new() 应该能处理大小异常,死循环处理申请成功及申请失败时的情况! 注意operator new会被继承,要注意derived中new运算符传进来的size,要判断是否为sizeof(Base) operator new [] 的size可能包含额外的空间用了来存储数组的大小。 operator delete同样要注意被继承的情况

52:placement new 和placement delete 要成对出现。 翻译为:特定版本的new 和特定版本的delete。指包含额外参数的new和delete。 注意两者需要成对出现,placement delete负责处理构造函数执行失败的内存释放。 注意相应的new和delete在继承关系中,要防止遮盖和被遮盖正常new、delete。解决方法是间接调用和using声明。

第9章:杂项讨论

53:不要忽视编译器警。 (作者说要严肃对待,但有不要过分依赖。我表示很头晕⊙0⊙)

54:熟悉标准程序库。 C++标准库包含C99标准、STL、iostream、locales、TR1中智能指针和正则表达式等。

55:熟悉Boost。 毛主席说:Boost是个好东西。不过在中国特色下,国外网站真的好难进呀(→_→)

结束

花了2个星期的终于写完了笔记,这样的效率,我不知道该哭还是该高兴。距离第一次看《Effective C++》有一个月了吧! 第二遍读,除了前几章,几乎是没有印象!好尴尬~ 尤其是模板编程的部分,由于实际使用少之又少,依然感觉晦涩难懂! 还有,感觉整篇文章下来用词都好“官方”呀,不接地气!缺少了自己的观点,缺少了几分的挑逗。

番外话:读研大半年了,确实看到了很多大神,大家都很厉害,整体水平较本科同学上升了一个大台阶。 但总觉得不对,总觉得缺少本科时的“精英文化”。可能是大家效率高,都有其他事情要忙吧, 但是我的基础差呀!感觉这样下去要找不到工作了。╯ω╰

时间:2016年4月25日19:59:56

https://www.douban.com/note/70449737/

Search

    Post Directory