Effective C++读书小记

1、视C++为一个语言联邦

对于内置类型而言,pass-by-value通常比pass-by-reference高效。()内置类型在按值传参时,只是将变量的值传递到栈上。 然后被调用函数将值取出后,使用即可。在按引用传参时,需要将被引用的变量的地址压栈, 然后被调用函数首先取出地址,然后再次根据地址寻址获取值。)

C++可分为四个部分:(1)C;(2)Objected-Orientated C++,面向对象的设计;(3)Template C++,泛型编程的部分;(4)STL。

注意:

(1)C++高效编程视情况而变化,取决于你使用C++的哪一部分。

2、尽量以const、enum、inline替换#define

注意:

(1)对于单纯常量,最好以const对象或者enum替换#define(便于调试,类型检查;有时能减少代码量)

(2)对于形似函数的宏(macros),最好改用inline函数替换#define(宏定义易错、难看,内联函数拥有宏的功能,却是一个真正的函数)

3、尽可能的使用const

注意:

(1)将某些东西设置为const能帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体;

(2)编译器强制实行“bitwise contness”,但你编写程序时应该使用“概念上的常量性”(conceptual constness);

(3)当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

4、确定对象使用前已经先被初始化

不同环境下,可能内置类型对象也没有自动初始化。

C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。

基类总是先于派生类被初始化,而类中成员变量总是以其声明的次序被初始化。

注意:

(1)为内置类型对象进行手工初始化,因为C++不保证初始化他们;

(2)构造函数最好使用成员初始化列(member initialization list),而不要在构造函数本体中使用赋值操作(assign)。初始化列列出的成员变量,其排列次序应该和它们在class中声明的次序相同;

(3)为免除“跨编译单元之初始化次序”问题,请以local static对象代替non-local static对象 。(将static对象包裹在一个函数中,这样可以避免在其它单元使用static对象时,该对象还没有初始化,调用函数能确保在第一次调用时初始化)。

5、了解C++默认编写并调用了哪些函数

对于一个空类,编译器会默认声明一个default构造函数、copy构造函数、copy assignment操作符和一个析构函数(最少是这4个),所有这些函数都是public且inline的。

class Empty{}

实质是:

class Empty{

public:

Empty(){...};

Empty(const Empty& rhs){...}

Empty& operator=(const Empty& rhs){...}

~Empty(){...}

}

只有这些函数需要时,它们才会被构造出来。

编译器产生的析构函数是non-virtual的,除非这个class的base class自身声明有virtual析构函数

只有生出的代码合法并且有适当表现机会时,默认生成的copy assignment构造函数才有意义。 (如base类的copy assignment构造函数 就是private,编译器将拒绝为其派生类生成一个copy assignment操作符,此外涉及const成员更改、指针是否更改指向等等操作也会死这样的效果)                                                              
                                               注意:

(1)编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符以及析构函数

6、若不想使用编译器自动生成的函数,就应该明确拒绝

两种方法:(1)将copy构造函数、copy assignment运算符函数声明为private(缺点:成员函数和友元函数还是能够使用)

(2)定义一个基类Uncopyable ,它的相关函数声明为private,其派生类相关的函数是无法自动生成的(因为编译器生成版总是会尝试调用其基类的对应兄弟。继承方式随意,基类析构函数不一定需要virtual,因为基类不含数据。)

注意:

(1)为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

7、为多态基类声明virtrual析构函数

C++明确指出,当派生类对象经由一个基类指针被删除(意思是多态,用基类指针指向派生类对象),而该基类带着一个non-virtual析构函数,其结果未定义——实际执行时通常发生的是对象的派生类成分没有被销毁,也就是派生类析构函数没有被调用,造成“局部销毁”。

解决方法:给基类一个virtual析构函数。

任何class只要带virtual函数都几乎确定应该也有一个virtual析构函数;如果class不含virtual函数,通常表示它并不意图被用作一个基类,此时令其析构函数为virtual往往是个馊主意。(虚函数的对象有虚函数表指针,占用内存空间;结构破坏,不具有移植性)

只有class内至少有一个virtual函数,才将它声明为virtual析构函数。

一个好的做法是为base classes提供一个pure virtual析构函数的声明(抽象类不能实例化,如果想实例化它,可行的做法是提供pure virtual析构函数一个定义)。

注意:

(1)polymorphic(带多态性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应当拥有一个virtual析构函数;

(2)classes的设计目的如果不是作为base classes使用,或不是为了具备多态性(polymorphically),就不该声明virtual析构函数。

8、别让异常逃离析构函数

通常在析构函数中释放分配的资源,如果销毁多个资源时,发生多个异常,程序执行结果会导致不明确的行为。

解决方法:(1)try...catch捕捉异常,调用std::abort()函数终止程序;(2)记录异常,继续执行。

如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。因为析构函数吐出异常是危险的,总会带来“过早结束程序”或“发生不明确行为”的危险。

注意:

(1)析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序;

(2)如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

9、决不在构造函数和析构函数中调用virtual函数

在base class构造期间,virtual函数不是virtual函数!即base class构造期间virtual函数绝不会下降到derived classes阶层。(原因:因为base class构造函数的执行更早于derived class构造函数,当base class构造函数执行时,derived class成员变量尚未初始化。)

在derived class对象的base classes构造期间,对象的类型是base class而不是derived class;同样对于析构函数,进入base class析构函数后对象就成为一个base class对象。

注意:

(1)在构造和析构期间不要调用virtual函数,因为该类调用从不下降至derived class(比起当前执行构造函数和析构函数的那一层)。

10、令operator=返回一个reference to *this

赋值操作符返回左侧对象的引用是为了实现“连锁赋值”!

这个只是协议,方便实现连锁赋值,并不是强制要求。

注意:

(1)令赋值(assignment)操作符返回一个reference to *this。

11、在operator=中处理“自我赋值”

自我赋值发生在对象被赋值给自己时。

注意:

(1)确保当对象自我赋值时operator=有良好的行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap;

(2)确定任何函数如果操作一个以上的对象,而其中多个对象时同一个对象时,其行为仍然正确。

12、复制对象时勿忘其每一个成分

如果你为class添加一个成员变量,你必须同时修改copying函数以及所有的构造函数(以符合第4条,成员变量使用前初始化)。

一旦你承担起“为derived class撰写copying函数”的重责大任,必须很小心地也复制其base class成分。那些成分往往是private,所以你无权访问它们,你应该让derived class的copying函数调用相应的base class函数。

所以,本条的意思是,当自己编写一个copying函数,请确保:(1)复制所有的local成员变量;(2)调用所有base classes内适当的copying函数。

不应该用copy assignment操作符调用copy构造函数。(这像是在试图构造一个已经存在的对象,某些情况下可以实现,但是某些情况下产生一些严重后果)

反过来,令copying构造函数调用copy assignment操作符,也同样无意义。(构造函数是用来初始化新对象,而copy assignment操作符是只施行于已初始化的对象身上)。

如果要重用相同的代码,最好的方式是新建一个新的成员函数给两者调用。(往往是private的init函数)

注意:

(1)copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”;

(2)不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

13、以对象管理资源

注意不要让多个auto_ptr同时指向同一元素。

对于auto_ptr :若通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一拥有权!

对于tr1::shared_ptr:是引用计数型智慧指针,它们的拷贝行为是正常的拷贝。

注意:

(1)为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源;

(2)两个较长使用的RAII classes分别是tr1::shared_ptr和auto_ptr,前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向null.

14、以对象管理资源在资源管理中小心copying行为

注意:

(1)复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为;

(2)普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法。不过其他行为也都可能被实现。

15、在资源管理类中提供对原始资源的访问

智能指针(tr1::shared_ptr和auto_ptr)都提供get()成员函数以供获取其原始底部指针(的复件),也重载了指针取值(pointer dereferencing)操作符(operator->和operator*),他们允许隐式转换至底部原始指针。

注意:

(1)APIs往往要求访问原始资源(raw resource),所以每一个RAII class应该提供一个“取得其管理之资源”的办法;

(2)对原始资源的访问可能经由显示转换和隐式转换。一般而言显示转换比较安全,当隐式转换对客户比较方便。

16、成对使用new和delete时要采用相同的形式

使用new时,有两件事发生:(1)内存被分配出来(通过operator new函数);(2)针对此内存会有一个(或多个)构造函数调用。

使用delete时,也会有两件事发生:(1)针对此内存会存在一个(或多个)析构函数被调用;(2)内存被释放。

尽量不要对数组形式做typedefs动作,因为这样有可能导致delete模糊。

注意:

(1)如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],那么一定不要在相应的delete表达式中使用[]。

17、以独立的语句将newed对象置入智能指针

同一条语句内的参数(临时函数调用)执行顺序在C++中是没有规定的,所以容易造成意想不到的后果,因此最好采用分离语句,这样次序就是固定的。

注意:

(1)以独立语句将newed对象存储于(置于)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。

18、让接口容易被正确使用,不易被误用

tr1::shared_ptr可以使用“专属删除器”。

注意:

(1)好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质;

(2)“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容;

(3)“阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任;

(4)tr1::shared_ptr支持定制型删除器(custom deleter)。这可防范DLL问题,可被用来自动解除互斥锁等等。

19、设计class犹如设计type

设计新的class,就是设计新的type,需要考虑一些问题

20、宁以pass-by-reference-to-const替换pass-by-value

pass-by-reference-to-const高效的原因:没有任何构造函数和析构函数被调用,因为没有任何新的对象被创建。(const是重要的,防止其被不经意修改)。

reference往往是以指针实现出来,因此pass-by-reference通常意味着真正传递的是指针。

对象小并不意味着其copy构造函数不昂贵。

注意:

(1)尽量以pass-by-referencee-to-const替换pass-by-value,前者通常比较高效,并可避免切割问题。

(2)以上规则并不适合内置类型,以及STL的迭代器和函数类型。对它们而言,pass-by-value往往比较适当。

21、必须返回对象时,别妄想返回其reference

注意:

(1)绝不要返回pointer或reference指向一个lock stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而可能需要多个这样的对象。

22、将成员变量声明为private

使用函数可以让你对成员变量的处理有更精确的控制;更重要的是封装(方便以后修改)。

protected成员变量的封装性是不是高过public成员变量?(否)

protected成员变量就像public成员变量一样缺乏封装性。

注意:

(1)切记将成员变量声明为private,这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性;

(2)protected并不比public更具封装性。(因为都没封装性)

23、宁以non-member、non-friend替换member函数

对于成员变量,越多函数可以访问它,那数据的封装性就越低,意味着我们改动时,影响到的代码就越多。因此推荐使用non-member non-friend函数。

此外,有利于扩展、分成多个头文件

注意:

(1)宁可拿non-member non-friend函数替换member函数,这样就可以增加封装性、包裹弹性和机能扩充性。

24、若所有的参数皆需要类型转换,请为此采用non-member函数

只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者,而this对象的隐喻参数,绝不是隐式转换的合格参与者。

无论何时,能够避免friend函数就避免。

注意:

(1)如果你需要为某个函数的所有参数(包括this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。

25、考虑写一个不抛出异常的swap函数

注意:

(1)当std::swap对你的类型效率不高时,提供一个swap成员函数,并确保这个函数不抛出异常;

(2)如果你提供一个member swap,也该提供一个non-member swap用来调用前者,对于classes(而非templates),也请特化std::swap

(3)调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命令空间资格修饰”;

(4)为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。

26、尽可能延后变量定义式的出现时间

不止应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止。这样,不仅能够避免构造(和析构)非必要对象,还可以避免无意义的default构造行为。

注意:

(1)尽可能延后变量定义式的出现,这样做可增加程序的清晰度并改善程序的效率。

27、尽量少做转型动作

转型(casts)破坏了类型系统,可能导致任何种类的麻烦,甚至有些非常晦涩。

旧式转型:(1)(T)expression;     (2)T(expression)

新式转型:(1)const_cast:通常被用来将对象的常量性移除(C++中唯一有此能力的转型);

(2)static_cast:用来强迫隐式转换,可以进行多种转换,但无法将const转为non-const(只有const_cast才能办到);

(3)dynamic_cast:主要用来执行“安全向下转型”,也就是用来决定某对象是否归属继承体系中的某个类型。(唯一无法被旧式转型执行的动作,可能耗费重大运行成本);

(4)reinterpret_cast:意图执行低级转换,实际动作及结果可能取决于编译器。

注意:

(1)如果可以,尽量避免转型,特别是在注重代码效率的代码中避免dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的替代设计。

(2)如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调出该函数,而不需将转型放进他们自己的代码内;

(3)宁可使用C++-style(新式)转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌。

28、避免返回handles指向对象内部成分

注意:

(1)避免返回handles(包括引用、指针、迭代器)指向对象内部。遵守这个条款可以增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”的可能性降到最低。

29、为“异常安全”而努力是值得的

注意:

(1)异常安全函数,即使发生异常也不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。

(2)“强烈保证”往往能以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义;

(3)函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。

30、透彻了解inlining的里里外外

注意:

(1)将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化;

(2)不要只因为function templates出现在头文件中,就将他们声明为inline。

31、将文件间的编译依存关系降至最低

如果使用object references或object pointer可以完成任务,就不要使用objects;如果可以,尽量以class声明式代替class定义式为声明式和定义式提供不同的头文件。

注意:

避免大量依赖性编译的解决方案就是:在头文件中用class声明外来类,用指针或引用代替变量的声明;在cpp文件中包含外来类的头文件。(只要在头文件中尽量减少include别的头文件,就可以做到尽量的减少依赖关系。)

(1)支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes;

(2)程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用。

32、确定你的public继承塑模出ls-a关系

virtual函数意味着“接口必须被继承”;non-virtual函数意味着“接口和实现都必须被继承”。

public inheritance(公开继承)意味着“is-a”(是一种)的关系。

派生类对象都是基类对象,反之不成立。意味着凡是需要基类对象的地方,可以使用派生类对象(注意这只对public继承才成立),而需要使用派生类对象的地方却不能使用基类对象。基类更具一般化,而派生类更具特殊化。

注意:

(1)“public继承”意味着is-a。适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象。

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

在Derived class中使用using Base::mf1会遮蔽Derived class中关于mf1所有的东西,使用Base class里的东西(并且public)。

转交函数会暗自成为inline。

注意:

(1)derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此;

(2)为了让被遮掩的名称再重见天日,可以使用using声明式或转交函数。

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

声明一个pure virtual函数的目的是为了让derived classes只继承函数接口;生成简朴的impure virtual函数的目的,是为了让derived classes继承该函数的接口和缺省实现;声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性实现。

注意:

(1)接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口;

(2)pure virtual函数只具体指定接口继承;

(3)简朴的(非纯)impure virtual函数具体指定接口继承和缺省实现继承;

(4)non-virtual函数具体指定接口继承及强制性实现继承。

35、考虑virtual函数以外的选择          --没看懂

注意:

(1)virtual函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。

(2)将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。

(3)tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物。

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

适用于基类对象的每一件事,也适用于派生类对象,因为每个派生类对象都是一个基类对象 ;如果“每个派生类对象都是一个基类对象”不为真,那么就不应该使用public形式继承。

注意:

(1)绝对不要重新定义继承而来的non-virtual函数。

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

virtual函数系动态绑定,而缺省参数值却是静态绑定。(原因是运行期效率,动态绑定时在编译期更慢且更复杂)

所谓静态类型,就是它在程序中被声明时所采用的类型;所谓动态类型,是指目前所指对象的类型。

为了不更改缺省参数值造成——代码重复,而且代码还有相依性——解决方案是:更改virtual函数设计,采用公共的non-virtual函数调用private的virtual函数。

注意:

(1)绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数-你唯一应该覆写的东西-却是动态绑定。

38、通过复合塑模出has-a或“根据某物实现出”

复合包括:分层、内含、集合和内嵌。

注意:

(1)复合的意义和public继承完全不同;

(2)在应用域,复合意味has-a(有一个)。在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出)。   --更像是“配接器”

39、明智而审慎的使用private继承

如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象;由private base class继承而来的所有成员,在derived class中都会变成private属性。

private继承意味着只有实现部分被继承,而接口部分被略去。

private继承意味着is-implemented-in-term-of(根据某物实现出)-----和复合一样:尽可能使用复合,必要时才使用private继承。

private继承时,派生类是可以使用基类中的public/protected成员,而派生类对象却不行(没有接口)!!!!

private继承有一个好处:空白基类优化(单继承时存在)

当你面对“并不存在is-a关系”的两个classes,其中一个需要访问另一个的protected成员,或需要重新定义一 个或多个virtual函数,private继承极有可能成为正统的设计策略。

注意:

(1)private继承意味is-implemented-in-term-of(根据某物实现出)。它通常比复合的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。

(2)和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。

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

使用virtual继承的那些classes说产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base classes的成员变量时,也比访问non-virtual base classes的成员变量速度慢——得为virtual继承付出代价。此外支配“virtual base classes初始化”的规则也比non-virtual bases的情况远为复杂且不直观。

使用忠告:(1)非必要不使用virtual bases;(2)如果你必须使用virtual base classes,尽可能避免在其中放置数据。

注意:

(1)多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要;

(2)virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况;

(3)多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private 继承某个协助实现的class”的两相组合。

41、了解隐式接口和编译期多态

隐式接口仅仅是由一组有效表达式构成,表达式自身可能看起来很复杂,但它们要求的约束条件一般而言相当直接而明确。

注意:

(1)classes和templates都支持接口(interfaces)和多态(polymorphism);

(2)对classes而言接口是显式的(explicit),以函数签名为中心。多态则是通过virtual函数发生于运行期;

(3)对templates参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过templates具现化和函数重载解析发生于编译器。

42、了解typename的双重意义

从C++的角度看,声明templates参数时,不论使用关键字class或typename,意义完全相同;然而class和typename并不总是等价的,有时一定要使用typename。

任何时候当你想要在templates中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置放上关键字typename;但是typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可以在member initialization list(成员初值列)中作为base class修饰词。

typename相关规则在不同的编译器上有不同的实践。

注意:

(1)声明templates参数时,前缀关键字class和typename可互换。

(2)请使用关键字typename标识嵌套从属类型名称,但不得在base class lists(基类列)或member initialization list(成员初值列)内以它作为base class修饰符。

43、学习处理模板化基类内的名称

base class是模板时,未特化前,派生类无法正常调用基类的成员(即编译器不会进入基类中搜索),这是因为基类在等待参数,还未特化(此时还是类模板),不是具体的类(模板类)。

要想解决这个问题,有三个解决方案:(1)在base class函数调用动作之前加上this->(这种方式是假设基类模板的所有特化版本都会包含该调用成员函数,如果最后特化的版本不包含,则编译报错);(2)使用using声明式using base<T>::func(原理和第一条一样); (3)明白指出调用的函数是位于base class内,base<T>::func()(原理和第一条一样)。

注意:

(1)可在derived class templates内通过“this->”指涉base class templates内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成。

44、将与参数无关的代码抽离template

class templates的成员函数只有被使用时才会被暗中具现化。

使用templates可能会导致代码膨胀。

注意:

(1)Templates生成多个classes和多个函数,所以任何templates代码都不该与某个造成膨胀的templates参数产生相依关系

(2)因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换templates参数;

(3)因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码。

45、运用成员函数模板接收所有兼容类型

在class内声明泛化copy构造函数(模板成员函数)并不会阻止编译器生成它们自己的copy构造函数(非模板成员函数),赋值(assignment)也一样。

注意:

(1)请使用成员函数模板生成“可接受所有兼容类型”的函数;

(2)如果你声明成员模板用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符。

46、需要类型转换时请为模板定义非成员函数

因为在templates实参推导过程中从不将隐式类型转换函数纳入考虑。

注意:

(1)当我们编写一个class templates,而它所提供之“与此templates相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class templates内部的friend函数”。

47、请使用traits classes表现类型信息

编译器条件语句:重载

注意:

(1)Traits classes使得“类型相关信息”在编译期可用。它们以templates和“templates特化”完成实现;

(2)整合重载技术后,traits classes有可能在编译期对类型执行if...else测试。

48、认识template元编程

模板元编程(TMP):编写templates-based C++程序并执行编译期的过程。即以C++写成、执行于C++编译器内的程序。

TMP循环效果是藉由递归完成的。TMP循环并不涉及递归函数的调用,而是涉及“递归模板具现化”。

TMP选择判断效果:可由函数重载实现。

注意:

(1)模板元编程可将工作由运行期移往编译期,因而得以实现早期侦测和更高的执行效率;

(2)TMP可被用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

49、了解new-handler的行为

当operator new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够内存。

一个良好的new-handler函数应该做以下事情:(1)让更多内存可被使用;(2)安装另一个new-handler;(3)卸厨new-handler,抛出异常;(4)抛出bad_alloc(或派生bad-alloc)异常,这个异常不会被operator new捕捉,因而传播到内存申请处;(5)不返回,通常调用abort或exit。

static成员必须在类外定义,除非它们是const 整数型。

C++不支持class专属new-handler,但是可以通过代码自己实现。

注意:

(1)set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用;

(2)Nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。

50、了解new和delete的合理替换时机

替换编译器提供的operator new或者operator delete的目的:(1)用来检测运行上的错误;(2)为了强化效能,使用特制版本;(3)为了搜集使用上的统计数据。

C++规则:所有operator new都应该内含一个循环,反复调用new-handler函数;operator new返回的指针都要有适当的对齐(取决于数据类型,malloc实现)。

注意:

(1)有许多理由需要写一个自定的new和delete,包括改善性能、对heap运用错误进行调试、搜集heap使用信息等。

51、编写new和delete时需固守常规

operator new实际上不只一次尝试分配内存,并在每次失败后调用new-handler函数。只有当new-handler函数指针是null时,operator new才会抛出异常。

如果即将被删除的对象派生自某个base class而后者缺乏virtual析构函数,那么C++传给operator delete的size_t数值可能不正确。也就是如果你的base classes遗漏virtual析构函数,operator delete可能无法正确运行。

注意:

(1)operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理0bytes申请。class专属版本则还应该处理“比正确大小更大的(错误)申请”;

(2)operator delete应该在收到null指针时不做任何事,class专属版本则还应该处理“比正确大小更大的(错误)申请”。

52、写了placement new也要写placement delete

new操作共有两个函数被调用:(1)用以分配内存的operator new;(2)构造函数。

placement new:带额外参数的operator new,也就是,如果operator new接受参数除了一定会有的那个size_t之外还有其他,这便是所谓的placement new。

如果内存分配成功,而构造函数抛出异常,运行期系统有责任取消operator new的分配并回复旧观,具体做法是,运行期系统寻找“参数个数和类型都与operator new相同”的某个operator delete,如果找到,就调用,如果找不到,就什么也不做。

规则:如果一个带额外参数的operator new没有“带相同额外参数”的对应版operator delete,那么当new的内存分配动作需要取消并恢复旧观时就没有operator delete会被调用。

此外,正常使用delete操作时调用的是正常形式的operator delete,而非其placement版本。这意味着,我们必须同时提供一个正常的operator delete(用于构造期间无任何异常被抛出)和一个placement delete版本(用以构造期间有异常被抛出)。

此外在继承体系中注意派生类中定义任何类型的placement new都会遮掩基类中的定义,所以如果不想遮掩,又需要有自己的placement new定义,请用using等方式取得基类相应的函数。

注意:

(1)当你编写一个placement operator new,请确定也写出了对应的placement operator delete、如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄漏;

(2)当你声明了placement new和placement delete,请确定不要无意识(非故意)地遮掩他们的正常版本。

53、请不要忽视编译器的警告

注意:

(1)严肃对待编译器发出的警告信息。努力在你的编译器的最高(最严)警告级别下争取“无任何警告”的荣誉;

(2)不要过度依赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。一旦移植到另一个编译器上,你原本依赖的警告信息有可能消失。

54、让自己熟悉包括TR1在内的标准程序库

注意:

(1)C++标准程序库的主要机能由STL、iostreams、locales组成,并包含C99标准程序库;

(2)TR1添加了智能指针(如tr1::shared_ptr)、一般化函数指针(tr1::function)、hash-based容器、正则表达式以及另外10个组件的支持;

(3)TR1自身只是一份规范,为获得TR1提供的好处,你需要一份实物。一个好的实物来源是Boost。

55、让自己熟悉Boost

注意:

(1)Boost是一个社群,也是一个网站。致力于免费、源码开放、同僚复审的C++程序库开发。Boost在C++标准化过程中扮演深具影响力的角色;

(2)Boost提供许多TR1组件实现品,以及许多其它程序库。

时间: 2024-10-08 11:55:54

Effective C++读书小记的相关文章

More Effective C++读书小记

1.仔细区别pointer和references 不论pointer或是references都使你间接参考其它对象. 没有所谓的null reference.一个reference必须总代表某个对象. 如果你有一个变量,其目的是用来指向(代表)另一个对象,但是也有可能它不指向(代表)任何对象,那么你应该使用pointer,因为你可以将指针设为null.换个角度,如果这个变量总是代表一个对象,也就是说你的设计并不允许这个变量为null,那么你应该使用reference. 由于reference一定

Effective Objective-C 读书笔记

一本不错的书,给出了52条建议来优化程序的性能,对初学者有不错的指导作用,但是对高级阶段的程序员可能帮助不是很大.这里贴出部分笔记: 第2条: 使用#improt导入头文件会把头文件的内容全部暴露到目标文件中,而且如果两个类之间存在循环引用则会出现编译错误,所以要尽量使用@class进行类声明. 如果需要实现一个协议,则必须#improt这个协议的头文件,所以可以将协议单独定义在一个.h文件当中.如果这个协议是代理模式协议的一部分,即需要与类捆绑使用才有实际意义,则建议定义在类当中,并以类名为前

东哥读书小记 之 《一个广告人的自白》

掰着指头一算,端午假期确实完成不少事情,过的太尼玛充实鸟: 去健身房2小时,且老夫的平板支撑终于能坚持超过1分钟,普大喜奔有木有: 给合租的室友买蛋糕过了个生日: 去 去哪儿 参加W3ctech的技术交流会: 精读<一个广告人的自白>: 正在读<卓有成效的管理者>: PS:此书不是光给管理者读的,而且俺目前更不是个管理者.可,其实每个人都是 自我的管理者,管理好自己是走向卓越的必要条件. 京东上挑了N久,买了几本书.鞋.运动穿的衣裤: 规划某Topic的技术方案初稿: 平均每天刷微

Effective Java 读书笔记(2创建和销毁对象)

第一章是引言,所以这里不做笔记,总结一下书中第一章的主要内容是向我们解释了这本书所做的事情:指导Java程序员如何编写出清晰.正确.可用.健壮.灵活和可维护的程序. 2.1考虑用静态工厂方法代替构造器 静态工厂方法与构造器相比有四大优势: (1)静态工厂方法有名称,具有适当名称的静态工厂方法易于使用.易于阅读: (2)不必每次在调用它们的时候都创建一个新的对象: (3)可以返回原返回类型的任何子类型的对象: (4)在创建参数化类型实例的时候,它们使代码变得更加简洁. 同时静态工厂方法也有两大缺点

Effective java读书札记第一条之 考虑用静态工厂方法代替构造器

对于类而言,为了让客户端获取它资深的一个实例,最常用的方法就是提供一个共有的构造器.还有一种放你发,也应该子每个程序员的工具箱中占有一席之地.类可以提供一个共有的静态 工厂方法,它只是返回类的实例的静态方法. 类可以通过静态工厂方法类提供它的客户端(对象),而不是通过构造器.提这样做的好处有: 1.静态工厂方法与构造器不同的第一大优势在于,它们有名称.比如构造器BigInteger(int,int,Random)返回的BigInteger可能为素数,如果用名为BigInteger.probabl

Effective Java读书笔记(4 类和接口)

4.1 使类和成员的可访问性最小化 要区别设计良好的模块和设计不好的模块,最重要的因素在于,这个模块对于外部的其他模块而言,是否隐藏其内部数据和其他实现细节.设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰的隔离开来,然后模块之间只通过API进行通信,一个模块不需要知道其他模块内部的工作情况,这个概念被称为信息隐藏或封装,是软件设计的基本原则之一. 4.2 在公有类中使用访问方法而非公有域 坚持面向对象程序设计思想:如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改

Effective Java读书笔记(3对于所有对象都通用的方法)

3.1 覆盖equals时请遵守通用约定 什么时候应该覆盖Object.equals()方法呢? 如果类具有自己特有的"逻辑相等"概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法. Object.equals()方法具有自反性.对称性.传递性.一致性和与null比较返回false的特点. 实现高质量equals方法的诀窍: (1)使用==操作符检查"参数是否为这个对象的引用".如果是,则返回true,这

Effective C++读书笔记之十二:复制对象时勿忘其每一个成分

Item 12:Copy all parts of an object 如果你声明自己的copying函数,意思就是告诉编译器你并不喜欢缺省显示中的某些行为.而编译器会对"你自己写出copying函数"做出一种复仇的行为:既然你拒绝它们为你写出copying函数,如果你的代码不完全,它们也不会告诉你.结论很明显:如果你为class添加一个成员变量,你必须同时修改copying函数.如果你忘记,编译器不太可能提醒你. 一下提供一种正确的模版: class Date{...}; class

Effective C++读书笔记之十三:以对象管理资源

Item 13:Use objects to manage resources 假设我们使用一个用来塑膜投资行为的程序库,其中各式各样的投资类型继承自一个root class: class Investment { ... };  //"投资类型"继承体系中的root class 进一步假设,这个程序系通过一个工厂函数(工厂函数会"返回一个base class指针,指向新生成的derived class 对象),供应我们某特定的Investment对象: Investment