读More Effective c++ 笔记
03.尽可能使用const
2023-04-22 16:55 | 页码:47
对于单纯常量,最好以const对象或cnums替换#defines。
对于形似函数的宏(macros),最好改用inline函数替换#defines
04.确定对象初始化
2023-04-22 16:56 | 页码:56
将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任
何作用域内的对象、函数参数、函数返回类型、成员函数本体。
编译器强制实施bitwise constness,但你编写程序时应该使用"概念上的常量性"(conceptual constness)。当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
这里需要注意,const可被const_cast函数拒绝掉
06.不自动生成的函数,就明确拒绝
2023-04-22 16:57 | 页码:67
编译器可以暗自为class创建default构造函数、copy构造函数、copyassignment操作符,以及析构函数。
需要留意,如果不需要以上的默认函数请明确指出,使用=delete。
2023-04-22 16:59 | 页码:69
为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。
07.多态基类声明virtual析构函数
2023-04-22 17:07 | 页码:70
1 | class{ |
2023-04-22 17:06 | 页码:70
问题出在getTimeKeeper返回的指针指向一个derived class对象(例如AtomicClock),而那个对象却经由一个base class指针(例如一个TimeKeeper*指针)被删除,而目前的base class(TimeKeeper)有个non-virtual析构函数。
2023-04-22 17:06 | 页码:71
1 | class TimeKeeper{ |
任何class带有virtual函数的都需要带有对应的virtual析构函数。
也就是子类解构的时候会自动调用父类.
polymorphic(带多态性质的)base classes应该声明一个virtual析构数。
如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性(polymorphically),就不该声明virtual析构函数。
09.不要在析构构造函数中调用virtual函数
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)。
10.operator= 返回 reference to *this
页码:83
令赋值(assignment)操作符返回一个referenceto*this
12.复制对象勿忘其每一个成分
页码:90
Copying函数应该确保复制"对象内的所有成员变量"及"所有base class成分"。不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个coping函数共同调用。
13. 以对象管理资源
页码:93
获得资源后立刻放进管理对象(managing object)内。以上代码中createInvestment返回的资源被当做其管理者auto_ptr的初值。实际上"以对象管理资源"的观念常被称为"资源取得时机便是初始化时机"(Resource)Acquisition Is Initialization;RAll),因为我们几乎总是在获得一笔资源,后于同一语句内以它初始化某个管理对象。有时候获得的资源被拿来赋值(而非初始化某个管理对象,但不论哪一种做法,每一笔资源都在获得的同同时立刻被放进管理对象中。管理对象(managing object)运用析构函数确保资源被释放。不论控制流如何离开区块,一旦对象被销毁(例如当对象离开作用域)其析构函数自然会被自动调用,于是资源被释放。如果资源释放动作可能导致抛出异常,事情变得有点棘手,但条款8已经能够解决这个问题,所以这里我们也就不多操心了。
14 资源管理类中小心coping行为
页码:96
为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。两个常被使用的RAll classes分别是trl::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向null。
15 对原始资源的访问
页码:99
复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现
16.承兑使用new和delete是要采取相同形式
页码:104
std::stringstringPtrl=new std::string;std::string stringPtr2=new std::string[100];//删除一个对象delete stringPtrl;//删除一个由对象组成的数组
delete[]stringPtr2;
页码:105
如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]
17.独立语句将new对象植入智能指针
页码:106
processWidget(std::trl::shared_ptr(new Widget),priority());令人惊讶的是,虽然我们在此使用"对象管理式资源"(objcoct-managingresources),上述调用却可能泄漏资源。稍后我再详加解释。
页码:107
std::trl::shared_ptrpw(new Widget);智能指针存储//newed所得对象。//这个调用动作绝不至于造成泄漏。processWidget (pw, priority());
页码:107
以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。
19.设计class犹如设计type
页码:114
新type的对象应该如何被创建和销毁?这会影响到你的class的构造函;数和析构函数以及内存分配函数和释放函数(operator new,operatornew[],opperator delete和operatordelete[]-见第8章)的设计,当然前提是如果你打算撰写它们。对象的初始化和对象的赋值该有什么样的差别?这个答案决定你的构造函数和赋值(assignment)操作符的行为,以及其间的差异。很重要的是别海淆了"初始化"和"赋值",因为它们对应于不同的函数调用(见条款4)。新type的对象如果被passedbyvalue(以值传递),意味着什么?记住,copy构造函数用来定义一个type的pass-by-value该如何实现。什么是新
type的"合法值"?对class的成员变量而言,通常只有某些数值集是有效的。那些数值集决定了你的class必须维护的约束条件(invvariants),也就决定了你的成员函数(特别是构造函数、赋值操作符和所谓"setter"函数)必须进行的错误检查工作。它也影响函数抛出的异常、以及(极少被使用的)函数异常明细列(exception specifications)
页码:115
你的新type需要配合某个继承图系(inheritance graph)吗?如果你继承图承自某些既有的classes,你就受到那些classes的设计的束缚,特别是受到"它们的函数是virtual或non-virtual"的影响(见条款34和条款36)。如果你允许其他classc继承你的class,那会影响你所声明的函数–尤其是析构函数–是否为virual(见条款7)。你的新type需要什么样的转换?你的type生存于其他一海票types之间因而彼此该有转换行为吗?如果你希望允许类型T1之物被隐式转换为类型T2之物,就必须在class T1内写一个类型转换函数(operator T2)或在class T2内写一个non-explicit-one-argument(可被单一实参调用)的构造函数。如果你只允许explicit构造函数存在,就得写出专门负责执行转换的函数,且不得为类型转换操作符(typeconversion operators)或non-explicit-one-argument构造函数。(条款15有隐式和显式转换函数的范例。)什么样的操作符和函数对此新type而言是合理的?这个问题的答案决定你将为你的class声明哪些函数。其中某些该是member函数,某些则否(见条款23,24,46)。什么样的标准函数应该驳回?那些正是你必须声明为private者(见条款6)。谁该取用新type的成员?这个提问可以帮助你决定哪个成员为public,哪个为protected,哪个为private。它也帮助你决定哪一个classes和/或functions应该是friends,以及将它们嵌套于另一个之内是否合理。什么是新type的"未声明接口"(undeclared interface)?它对效率异常安全性(见条款29)以及资源运用(例如多任务锁定和动态内存)提供何种保证?你在这些方面提供的保证将为你的class实现代码加上相应的约束条件。你的新type有多么一般化?或许你其实并非定义一个新type,而是定义一整个types家族。果真如此你就不该定义一个新class,而是应该定义一个新的class template.
页码:116
你真的需要一个新type吗?如果只是定义新的derivedclass以便为既有的class添加机能,那么说不定单纯定义一或多个non-member函数或templat,更能够达到目标。这些问题不容易回答,所以定义出高效的classes是一种挑战。然而如果能够设计出至少像C++内置类型一样好的用户自定义(user-defined)classes,一切汗水便都值得。请记住Class的设计就是type的设计。在定义一个新type之前,请确定你已经考虑过本条款覆盖的所有讨论主题。
合理的设计类.
20. 传引用替代传值
页码:120
尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题(slicing problem)。以上规则并不适用于内置类型,以及STL的迭代器和函数双象。对它们而言,pass-by-value往往比较适当。主要是考虑性能因素。
21.必须返回对象时,别返回引用
页码:124
绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static象而有可能同时需要多个这样的对象。条款4已经为"在单线程环境中合理返回reference指向一个local static对象"提供了一份设计实例。
主要是考虑安全因素,一般使用的是智能指针,引用传递。
22.成员变量声明为private
页码:128
切记将成员变量声明为private。这可赋予客户访问数据的一致性、可微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性生。protected并不比public更具封装性。
23.以non-member,non-friend 替换member函数
24.所有参数皆需类型转换,采用non-member 函数
页码:135
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member
25. 考虑写出一个不抛异常的swap函数
页码:142
当std::swap对你的类型效率不高时,提供一个swap成员函数,确定这个函数不抛出异常。如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于classes(而非templates),也请特化std::swap。调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何"命名空间资格修饰"。为"用户定义类型"进行stdtemplates全特化是好的,但千万不要试在std内加入某些对std而言全新的东西。
27:尽量少做转型动作
页码:146
尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。
页码:147
const_cast通常被用来将对象的常量性转除(cast away the constness)。它也是唯一有此能力的C+±style转型操作符。dynamic_cast主要用来执行"安全向下转型"(safe downcasting),t就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作(稍后细谈)。。reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就表示它不可移植。例如将一个pointer to int转型为一个int这一类转型在低级代码以外很少见。本书只使用一次,那是在讨论如何针对原始内存(rawmemory)写出一个调试用的分配器(debugging allocator)时,见条款50static_cast用来强迫隐式转换(implicitconversions),例如将non-const对象转为const对象(就像条款3所为),或将int转为double等等它也可以用来执行上述多种转换的反向转换,例如将void*指针转为typed指针,将pointer-to-base转为pointer-to-derived.但它无法将const转为non-const–这个只有const_cast才办得到。
一般很少会用到reinterpret_cast,除非你明确知道你再做什么。
28:避免返回handles指向对象内部成分
页码:153
如果可以,尽量避免转型,特别是在注重效率的代码中避免dynanic_casts.如果有个设计需要转型动作,试着发展无需转型的替代设计。如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内。宁可使用C+±style(新式)转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌。
页码:156
请记住避免返回handles(包括references、指针、迭代器)指向对象内部遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生"虚吊号码牌"(dangling handles)的可能性降至最低。
30:透彻了解inlining的里里外外
页码:164
异常安全函数(Exception-safe functions)即使发生异常也不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。"强烈保证"往往能够以copy-and-swap实现出来,但"强烈保证"并非对所有函数都可实现或具备现实意义。函数提供的"异常安全保证"通常最高只等于其所调用之各个函数的"异常安全保证"中的最弱者。
31:文件间的编译依存关系降至最低
页码:178
支持"编译依存性最小化"的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。程序库头文件应该以"完全且仅有声明式"(full and declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用。
32:public继承
页码:185
"public继承"意味is-a。适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象。
Public继承:基类的public和protected成员在派生类中保持原来的访问级别。基类的private成员在派生类中不可访问。
Protected继承:基类的public和protected成员在派生类中都变为protected。基类的private成员在派生类中不可访问。
Private继承:基类的public和protected成员在派生类中都变为private。基类的private成员在派生类中不可访问。
33:避免遮掩继承而来的名称
页码:186
global变量x,因为内层作用域的名称会遮掩(遮蔽)外围作用域的名称。我们可以这样看本例的作用域形势:
37:不重定义继承而来的缺省参数值
页码:210
绝对不要重新定义继承而来的non-virtual函数。
页码:210
virtual函数系动态绑定(dynamically bound),而缺省参数值却是静态绑定(statically bound)。
38:符合塑模出has-a
页码:213
绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数–你唯一应该覆写的东西–却是动态绑定。
39:明智而审慎的使用private继承
页码:217
:如果classes之间的继承关系是private,编译器不会自动将一个derived cass对象(例如Student)转换为一个base class对象(例如Person)。这和public继的情况不同。这也就是为什么通过s调用eat会失败的原因。第二条规则是,由private baseclass继承而来的所有成员,在derived class中都会变成private属性,使它们在base class中原本是protected或public属性。
页码:217
继承意味implementedin-terms-of(根据某物实现出)。如果你让class D以private形式继承class B,你的用意是为了采用classB内已经备妥的某些特性,不是因为B对象和D对象存在有任何观念上的关系。private继承纯粹只是一种实现技术(这就是为什么继承自-一个private baseclass的每样东西在你的class内都是private:因为它们都只是实现枝节而已)。借用条款34提出的术语,private继承意味只有实现部分被继承,接部分应略去。如果D以private形式继承B,意思是D对象根据B对象实现而得,再没有其他意涵了。Private继承在软件"设计"层面上没有意义,其意义只及于软件实现层面
39:明智而审慎的使用private继承
页码:220
这个激进情况真是有够激进,只适用于你所处理的class不带任何数据时。这样的classes没有non-static成员变量,没有virtual函数(因为这种函数的存在会为每个对象带来一个vptr,见条款7),也没有virtual base classes(E因为这样的baseclasses也会招致体积上的额外开销,见条款40)。于是
页码:221
通常C++官方勒令默默安插一个char到空对象内。然而齐位需求(alignment,见条款50)可能造成编译器为类似HoldsAnI这样的class加上一些衬垫(padding),所以有可能HoldsAnInt对象不只获得一个char大小,也许实际上被放大到足够又存放一个int
40:多重继承
页码:222
Private继承意味is-implemented-in-terms of(根据某物实现出)。它通常比复合(composition)的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。和复合(composition)不同,private继承可以造成empty base最优化。这对致力于"对象尺寸最小化"的程序库开发者而言,可能很重要。
页码:224
我对virtual base classes(亦相当于对virtual继承)的忠告很简单。第一,非必要不使用virtual bases。平常请使用non-virtual继承。第二,如果你必须使用virtualbase classes,尽可能避免在其中放置数据。这么一来你就不需担心这些classes身上的初始化(和赋值)所带来的诡异事情了。Java
42:typename双重意义
页码:233
classes和templates都支持接口(interfaces)和多态(polymorphism)。对classes而言接口是显式的(explicit),以函数签名为中心。多态则是通过virtual函数发生于运行期。对template参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期。
页码:234
template<typenamevoid print2nd(const C& container)C::const_iterator* x;看起来好像我们声明x为一个local变量,它是个指针,指向一个C::const_iterator。但它之所以被那么认为,只因为我们"已经知道"C::const_iterator是个类型。如果C::const iterator不是个类型呢?如果有个static成员变量而碰巧被命名为const_iterator,或如果x碰巧是个global
页码:235
变量名称呢?那样的话上述代码就不再是声明一个local变量,而是一个相乘动作:C::const_iterator乘以x。当然啦,这听起来有点疯狂,但却是可能的,而撰写C++解析器的人必须操心所有可能的输入,甚至是这么疯狂的输入。
43:处理模板化基类内的名称
页码:237
声明template参数时,前缀关键字class和typename可互换。请使用关键字typename标识嵌套从属类型名称;但不得在base classlists(基类)列或member initialization list(成员初值列)内以它作为base clas修饰符
44:与参数无关的代码抽离templates
页码:242
在derived class templates内通过"this->"指涉base class templates内的成员名称,或藉由一个明白写出的"base class资格修饰符"完成。
页码:247
Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。因非类型模板参数(non-type template parameters)而造成的代码膨胀。往往可消除,做法是以函数参数或class成员变量替换template参数。因类型参数(type parameters)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representations)的具现类型(instantition types)共享实现码。
46:需要类型转换时请为模板定义非成员函数
页码:252
请使用member function templates(成员函数模板)生成"可接受所有兼容类型"的函数。如果你声明member templates用于"泛化copy构造"或"泛化assignment操作"你还是需要声明正常的copy构造函数和copy assignment操作符。
48:认识template元编程
页码:268
Template meta programming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。TMP可被用来生成"基于政策选择组合"(based on combinationsof policychoices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。
54:熟悉TR1在内的标准程序库
页码:293
严肃对待编译器发出的警告信息。努力在你的编译器的最高(最严苛)警告级别下争取"无任何警告"的荣誉。不要过度倚赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。一旦移植到另一个编译器上,你原本倚赖的警告信息有可能消失。