1. 视C++为一个语言联邦
C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。
2. 尽量以const,enum。inline替代#define
1) 对于单纯常量,最好以const对象或enum替换#define
2) 对于形似函数的宏。最好改用inline函数替换#define
3)宁能够编译器替换预处理器
4)用define定义的名称并没有进入符号表。无法对其进行跟踪
3. 尽可能使用const
1)假设keywordconst出如今星号左边。表示被指物是常量;右边,指针本身是常量;星号两边,被指物和指针都是常量。
2)将某些东西声明为const可帮助编译器侦測出错误使用方法。const可被施加于不论什么作用域内的对象、函数參数、函数返回类型、函数成员本体。
2)编译器强制bitwise constness,但你编敲代码时应用使用“概念上的常量性”
3)当const和non-const成员函数有着实际等价的实现时,令non-const版本号调用const版本号可避免代码反复。
4)const_iterator所指的东西不可被修改。
4. 确定对象被使用前被初始化
1)成员初始化次序固定。父类早于子类。成员变量以其声明次序初始化
2) 为内置型对象进行手工初始化,由于C++不保证初始化它们。
3)构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。
初值列列出的成员变量。其排列次序应该和它们在class中声明次序同样。
4)为免除“跨编译单元之初始化次序”问题。请以local static对象替换non-local static对象。
5)总是使用成员初始化列表
6)类成员初始化次序是基类先于派生类,类的成员变量总是以声明顺序被初始化。
5. 了解C++默默编写并调用哪些函数
1)编译器能够暗自为class创建default构造函数、copy构造函数、copy assignment操作符。以及析构函数。
2)对于内含引用成员的类应自定义copy assignment操作符。
6. 若不想使用编译器自己主动生成的函数,就应该明白拒绝。
1)将连接期错误移至编译期是可能的(并且那是好事。毕竟越早侦測出错误越好)
2)为驳回编译器自己主动(暗自)提供的功能,可将对应的成员函数声明为private而且不予实现。使用像noncopyable这种base class也是一种做法。
7。 为多态基类声明virtual析构函数
1)工厂函数返回的对象必须位于heap,因此为了避免泄漏内存和其它资源。将工厂函数返回的每个对象适当的delete掉非常重要。
TimeKeeper* ptk = getTimerKeeper();delete ptk;
2)假设class不含virtual函数,通常表示它并不意图被用做一个base class。当class不企图被当作base class,令其析构函数为virtual往往是馊主意。由于会无端添加一个虚函数指针。多占用空间并且不再与C中的结构体有相同的内存表示。 3)很多人的心得是:仅仅有当class内含至少一个virtual函数,才为他声明virtual析构函数。
4)polymorphic(带多态性质)base class应该声明一个virtual析构函数。
假设class带有不论什么virtual函数,它就应该拥有一个virtual析构函数。
5)classes的设计目的假设不是做为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。
6)假设你以前企图继承一个标准容器或不论什么其它“带有non-virtual析构函数”类,拒绝诱惑吧。
8.别让异常逃离析构函数
1)析构函数绝对不要吐出异常。假设一个析构函数调用的函数可能抛出异常,析构函数应该捕捉不论什么异常,然后吞下它们(不传播)或结束程序。
2)假设客户须要对某个操作函数执行期间抛出的异常做出反应。那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
9.绝不在构造和析构函数中调用virtual函数
1)在基类构造期间。虚函数不是虚函数,这样就会调用基类的成员而不是派生类的,即使是派生类调用的基类构造函数。
2)在调用基类析构函数时。派生类的成员已被析构。这样就会将对象视为基类对象,虚函数不再是虚函数。
3)绝不在构造和析构函数中调用virtual函数,由于这类调用从不下降至派生类。
10.令operator=(赋值运算符)返回一个reference to *this
11.在operator=中处理“自我赋值”。即将值赋值给自己
1)确保当对象自我赋值时operator=有良好行为。当中技术包含比較“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
2)确定不论什么函数假设操作一个以上对象,而当中多个对象是同一个对象时,其行为仍然正确。
12.复制对象时勿忘其每个成分
1)copying函数应该确保复制“对象内的全部成员变量”及“全部base class成分”。
copying函数指copy构造函数和copy assignment操作符
2)不要尝试以某个copying函数实现还有一个copying函数。
应该将共同技能放进第三个函数中,并由两个copying函数共同调用。
13.以对象管理资源
1)获得资源后立马放进管理对象内。
2)管理对象运用析构函数确保资源被释放。
3)auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]。
因此在动态分配而得的array身上使用auto_ptr或tr1::shared_ptr是个馊主意。array能够用vector或string。
4)为防止资源泄露。请使用RAII(Resource Acquisition Is Intialization)对象,它们在构造函数中获得资源并在析构函数中释放资源。
5)两个常被使用的RAII类各自是auto_ptr和tr1::shared_ptr。后者一般是较佳选择,由于其copy行为比較直观。若选择auto_ptr,复制动作会使它(被复制物)指向null。
6)STL容器要求其元素发挥“正常”复制行为。因此这些容器容不得auto_ptr。
7)RCSP(引用计数型智能指针)提供的行为类似垃圾回收,但无法打破环状引用。
14.在资源管理类中小心coping行为(建立自己的资源管理类)
1)当一个RAII对象被复制,会发生什么?能够有下面两种可能:
a。
禁止复制。比如。相互排斥锁被复制。
b。对底层资源祭出“引用计数法”,如tr1::shared_ptr。tr1::shared_ptr的缺省行为是“当引用计数为0时删除其所指物”。比如对于相互排斥锁我们希望的释放动作是解除锁定而非删除。能够指定其所谓的deleter。
2)复制底部资源,即深拷贝
3)转移底部资源的拥有权。
若希望仅仅有一个RAII对象指向一个未加工资源(raw resource),能够让资源的拥有权从被复制物转移到目标物。
4)复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
5)普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法。
只是其它行为也都可能被实现。
6)并不是全部资源都是heap_based,对那种资源而言,智能指针往往不适合作为资源掌管者。
15。在资源管理类中提供对原始资源的訪问。
1)auto_ptr和tr1::shared_ptr都提供一个get成员函数,用来运行显式转换,也就是它会返回智能指针内部的原始指针。
2)API往往要求訪问原始资源,所以每个RAII类应该提供一个取得其所管理资源的方法。
3)对原始资源的訪问可能经由显式转换或隐式转换。一般而言显式转换比較安全,但隐式转换对客户比較方便。
16.成对使用new和delete时要採取同样形式。
1)假设在new表达式中使用[]。必须在对应的delete表达式中使用[]。假设在new表达式中不使用[],一定不要在对应的delete表达式中使用[]。
17.以独立语句将new出来的对象置入智能指针
1)以独立语句将new出来的对象置入智能指针内。假设不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。
18.让接口easy被正确使用,不易被误用。
1)理想上。假设客户企图使用某个接口而却没有获得他所预期的行为,这个代码不该通过编译;假设代码通过了编译,它的作为就该是客户所想要的。
2)不论什么接口假设要求客户必须记得做某些事情,就是有着“不对使用”的倾向。
3)促进正确使用的办法包含接口的一致性,以及与内置类型的行为兼容。
4)阻止误用的办法包含建立新类型、限制类型上的操作。束缚对象值,以及消除客户的资源管理责任。
5)tr1::shared_ptr支持定制型删除器。这可防范DLL问题。可被用来自己主动解除相互排斥锁。
19.设计class宛如设计type
20.宁以pass-by-reference-to-const替换pass-by-value
1)尽量以pass-by-reference-to-const替换pass-by-value。前者通常比較高效,并可避免分割问题。
2)以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比較适当。
21.必须返回对象时,别妄想返回其reference
1)绝不要返回local stack对象的指针或引用。或堆对象的引用,或返回一个local static对象的指针或引用而有可能同一时候须要多个这种对象。
22.将成员变量声明为private
1)切记将成员变量声明为private。这可赋予客户訪问数据的一致性、可细微划分訪问控制、允诺约束条件获得保证。并提供class作者以充分的实现弹性。
2)protected并不比public更具封装性
23.宁以non-member、non-friend替换member函数
1)宁以non-member、non-friend替换member函数,这样做能够添加封装性、包裹弹性和机能扩充性
24.若全部參数皆需类型转换,请为此採用non-member函数(例:重载乘法运算符,能够定义为member、friend或non-member,好的是non-member)
25.考虑写出一个不抛异常的swap函数。
1)当std::swap对你的类型效率不高时。提供一个swap成员函数,并确定这个函数不抛出异常
2)假设你提供一个成员函数。也应该提供一个非成员函数来调用前者。对于class而非模板,也请特化std::swap
3)调用swap时应针对std::swap应用using声明式,然后调用swap而且不带不论什么“命名空间资格修饰”
4)为“用户自己定义类型”进行std templates全特化是好的,但千万不要尝试在std内增加某些对std而言全新的东西。
26.尽可能延后变量定义式的出现时间
1)尽可能延后变量定义式的出现时间。这样做可添加程序的清晰度并改善程序效率
2)尽量延后直到可以给他初值实參为止。太早的话会先调用default构造,然后再赋值,比copy构造多一次调用。
3)除非你知道赋值成本比“构造+析构”成本低且你正在处理的代码效率高度敏感,你应该把变量定义放在循环里。
循环内:n个构造 + n个析构; 循环外: 1个构造 + 1个析构 + n个赋值。
27.尽量少做转型动作
1)
2)假设能够,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。假设有个设计须要转型动作。试着发展无需转型的动作设计
3)假设转型是必要的,试着将它隐藏于某个函数背后。客户随后能够调用该函数,而不需将转型放在他们自己的代码里。
4)宁可使用c++_style(新式)转型,不要使用旧式转型。前者非常easy辨识出来,并且也比較有着分门别类的职掌。
28避免返回handles指向对象内部成分
1)避免返回handles(包含引用、指针、迭代器)指向内部对象。
遵守这个条款可添加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handles)的可能性降到最低。
29.为异常安全而努力是值得的
1)异常安全函数提供下面三个保证之中的一个:
a。基本承诺:假设异常被抛出,程序内的不论什么事物仍然保持在有效状态下。没有不论什么对象或数据结构会因此而败坏,全部对象都处于一种内部前后一致的状态。
然后程序的现实状态可能无法预料。
b。
强烈保证:假设异常被抛出,程序状态不改变。调用这种函数须有这种认知:假设函数成功。就是全然成功,假设函数失败,就会恢复到调用函数之前的状态。
c。不抛掷保证:承诺绝不抛出异常,由于它们总是可以完毕承诺的功能。
2)异常安全函数即使发生异常也不会泄漏资源或同意不论什么数据结构败坏。这种函数区分为三种可能的保证:基本型、强烈性、不抛异常型
3)强烈保证往往可以以copy-and-swap实现出来,但强烈保证并不是对全部函数都可实现或具备现实意义
4)函数提供的异常安全保证通常最高仅仅等于其所调用之各个函数的异常安全保证中的最弱者。
30.透彻了解inlining的里里外外
1)inline仅仅是对编译器的一个申请,不是强制命令。
这项申请能够隐喻提出。也能够明白提出。隐喻方式是将函数定义于class定义式内,定义于class内的friend函数也是。
2)将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更easy,也可使潜在的代码膨胀问题最小化,是程序的速度提升机会最大化。
3)不要仅仅由于function templates出如今头文件,就将它们声明为inline
4)虚函数不会被inline,由于虚函数是在执行时动态绑定。而inline是编译时展开
5)指针指向的函数不会被inline。由于无法对其取地址。
6)改变inline函数后,应用到该函数的客户程序都得又一次编译,而非inline函数仅仅需又一次连接,若实现为dll,连接都不须要。
7)非常难在inline函数中设置断点,不好调试
31.将文件间的编译依存关系降至最低。
1)支持编译依存性最小化的一般构想是:相依于声明式。不要相依于定义式。
基于此构想的两个手段是Handle classed和Interface Classes
2)程序库头文件应该以“全然且仅有声明式”的形式存在。这样的做法不论是否涉及templates都适用。
32.确定你的public继承塑膜出 is-a 关系
1)public继承意味着 is-a 。适用于base classes身上的每一件事情一定也适用于derived class身上,由于每个derived class对象也都是一个base class对象。
33.避免遮掩继承而来的名称
1)derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。
2)为了让被遮掩的名称再见天日。可使用using声明式或转交函数(forwarding functions)。
3)即使基类和派生类内函数有不同的參数类型。并且不论是虚函数还是非虚函数。基类函数都会被派生类同名函数遮掩。
34.区分接口继承和实现继承
1)接口继承和实现继承不同。在public继承之下,派生类总是继承基类的接口
2)纯虚函数仅仅详细指定接口继承
3)简朴的(非纯)虚函数详细指定接口继承和缺省实现继承
4)非虚函数详细指定接口继承和强制性实现继承
35.考虑虚函数以外的其它选择
1)虚函数的替代方案包含NVI手法及策略设计模式的多种形式。NVI手法自身是一个特殊形式的模板方法设计模式。
2)将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法訪问class的non-public成员。
3)tr1::function对象的行为就像一般函数指针。这种对象可接纳“与给定目标签名式兼容”的全部可调用物。
36.绝对不要又一次定义继承而来的非虚函数。
37.绝不又一次定义继承而来的缺省參数值
1)绝不又一次定义继承而来的缺省參数值。由于缺省參数值是静态绑定的。而虚函数却是动态绑定的。
38.通过复合塑模出has-a或“依据某物实现出”
1)复合的意义和public继承全然不同。
2)在应用域。复合意味着has-a。在实现域,复合意味着is-implemented-in-terms-of(依据某物实现出)。
39.明智而审慎地使用private继承
1)private继承并不意味着is-a关系。
2)private继承意味着仅仅有实现部分被继承,接口部分应略去。
3)private继承意味着is-implemented-in-terms-of(依据某物实现出)。同复合语义相似,应该尽可能使用复合,必要时才使用private继承。
4)EBO(Empty Base Optimization),空白基类最优化。派生类继承空白基类后的大小等于派生类本身成员的大小。EBO一般在单一继承下才可行。这对致力于“对象尺寸最小化”的程序库开发人员而言。可能非常重要。
5)两个不存在is-a的类,当中一个须要訪问还有一个的protected成员,或须要又一次定义一个或多个virtual函数。private继承极有可能成为正统设计策略。
40.明智而审慎地使用多重继承
1)如非必要。不要使用虚基类。假设必须使用虚基类,尽可能避免在当中放置数据,这样就不会操心类初始化和赋值所带来的诡异的事情了。
2)多重继承比单一继承复杂。它可能导致新的歧义性,以及对虚继承的须要。
3)虚继承会添加大小、速度、初始化及赋值复杂度等等成本。
吐过虚基类不带不论什么数据。将是最具有用价值的情况。
4)多重继承的确有正当用途。当中一个情节涉及“public继承某个接口类”和“private继承某个协助实现的类”的两相结合。
41.了解隐式接口和编译时多态
1)类和模板都支持接口和多态
2)对类而言接口是显式的,以函数签名为中心。
多态则是通过虚函数发生于执行期。
3)对模板參数而言,接口是隐式的,奠基于有效表达式。多态则是通过模板具现化和函数重载解析发生于编译期。
42.了解typename的双重意义
1)不论什么时候当你想要在模板中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置放上keywordtypename。
2)声明模板參数时。前缀keywordclass和typename可互换。
3)请使用keywordtypename标识嵌套从属类型;但不得在base class lists和member initialization list内以它为base class修饰符。
43.学习处理模板化基类内的名称
1)可在派生类模板内通过"this->"指涉基类模板内的成员名称,或借由using声明式,或借由一个明确写出的“基类资格修饰符”完毕。
44.将与參数无关的代码抽离模板
1)模板生成多个类和多个函数。所以不论什么模板代码都不该与某个造成膨胀的模板參数产生相依关系
2)因非类型模板參数而造成的代码膨胀,往往可消除,做法是以函数參数或类成员变量替换模板參数
3)因类型參数而造成的代码膨胀,往往可减少。做法是让带有全然同样二进制表述的具现类型共享实现码。
45.运用成员函数模板接受全部兼容类型
49.了解new-handler的行为
1)当operator new无法满足某一内存需求时,它会抛出异常。
2)set_new_handler同意客户指定一个函数,在内存分配无法获得满足时被调用。
3)nothrow new是一个颇为局限的工具。由于它仅仅适用于内存分配;后继的构造函数调用还是可能抛出异常。
50.了解new和delete的合理替换时机。
1)有很多理由须要写个自定的new和delete,包含改善效能、对heap运用错误进行调试、收集heap使用信息。
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组件实现品,以及其它很多程序库。