章节 | 标号 | 条款 | 细节 |
让自己习惯C++ | Item-1 | 视C++为一个语言联邦 | |
Item-2 | 尽量以const、enum、inline替换#define |
| |
Item-3 | 尽可能使用const |
| |
Item-4 | 确定对象被使用前已先被初始化 | 为内置型对象进行手工初始化,C++不保证初始化它们; 构造函数最好使用成员初值列,而不要在构造函数体内使用赋值。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。 C++规定:对象的成员变量的初始化动作发生在构造函数本体之前。 C++对定义于不同编译单元内的non-local static对象的初始化次序并无明确定义。 函数内的static对象称为local static对象;其他的static对象都是non-local static对象。 对于内置型对象:初始化和赋值成本相同。 为避免跨编译单元之初始化次序问题,请以local static对象替换non-local static对象:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static),这些函数返回一个reference指向它所包含的对象。然后用户调用这些函数,而不是直接指涉这些对象。 | |
构造/析构/赋值 | Item-5 | 了解C++默默编写并调用那些函数 |
|
Item-6 | 若不想使用编译器自动生成的函数,则明确拒绝 | 所有编译器产生的函数都是public,为了阻止这些函数被自动创建出来,只能先自行声明他们。我们可以将这些函数(copy构造函数或copy assignment操作符)声明为private,阻止他们被调用。
| |
Item-7 | 为多态基类声明virtual析构函数 |
| |
Item-8 | 别让异常逃离析构函数 |
| |
Item-9 | 绝不在构造和析构过程中调用virtual函数 |
| |
Item-10 | 令operator=返回一个reference to *this | 为了实现“连锁赋值”:赋值操作符必须返回一个reference指向操作符的左侧实参。 | |
Item-11 | 在operator=中处理“自我赋值” | 确保当对象自我赋值时operator=有良好的行为:证同测试、精心周到的语句顺序、copy-and-swap。 | |
Item-12 | 复制对象时务忘其中每一个成分 |
| |
资源管理 | Item-13 | 以对象管理资源 | 把资源放进对象内,我们便可依赖C++的“析构函数自动调用机制”确保资源被释放。 以对象管理资源的两个关键想法:
auto_ptr:若通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一拥有权。 由于auto_ptr被销毁时会自动删除它所指之物,所以我们一定要注意别让多个auto_ptr同时指向同一对象。 引用计数型智慧指针(RCSP):所谓RCSP也是个智能指针,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。例如tr1::shared_ptr auto_ptr和tr1::shared_ptr 两者都在析构函数中做delete而不是delete[]动作,这意味着在动态分配而得的array上使用auto_ptr和tr1::shared_ptr是个bad idea。
|
Item-14 | 在资源管理类中小心copying行为 | 常见的RAII class copying行为:
| |
Item-15 | 在资源管理类中提供对原始资源的访问 | 显式转换: auto_ptr和tr1::shared_ptr都提供一个get成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针(的复件) 隐式转换: 就像(几乎)所有智能指针一样,auto_ptr和tr1::shared_ptr也重载了指针取值 (pointer dereferencing) 操作符 (operator->和 operator*) ,它们允许隐式转换至底部原始指针。 自定义资源管理类,可以定义隐式转换函数 operator OriginClass(){...} | |
Item-16 | 成对使用new和delete时要采取相同形式 |
数组所用的内存通常还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。唯一能让delete知道内存中存在一个“数组大小”的记录方法是使用delete[]。 | |
Item-17 | 以独立语句将newed对象置入智能指针 | 因为编译器对于“跨越语句的各项操作”没有重新排列的自由(只有在语句内编译器才拥有那个自由度); 以独立语句将newed的对象存储于智能指针内,如果不这样做,一旦抛出异常,则可能导致内存泄露(在资源被创建和资源被转换为资源管理对象两个时间点之间可能会发生异常)。 | |
设计与声明 | Item-18 | 让接口容易被正确使用,不易被误用 |
所谓的 "cross-DLL problem“ :这个问题发生于“对象在动态连接程序库(DLL) 中被new创建,却在另一个DLL内被 delete销毁"。在许多平台上,这一类“跨DLL new/delete 成对运用“会导致运行期错误。 tr1::shared_ptr 没有这个问题,因为它缺省的删除器是来自“tr1::shared_ptr诞生所在的那个 DLL"的delete 。tr1::shared_ptr会追踪记录所指对象的引用计数变成0时所需要调用的那个DLL's delete。 |
Item-19 | 设计class犹如设计type | 应该带着和“语言设计者当初设计语言内置类型时”一样的谨慎来研讨class的设计。 你的新type需要什么样的转换?你的 type 生存于其他一海票types之间,因而彼此该有转换行为吗?
| |
Item-20 | 宁以pass-by-reference-to-const替换pass-by-value | 除非另外指定,否则函数参数都是以实际参数的副本为初值,而调用端所获得的亦是函数返回值的一个副本。这些副本由对象的copy构造函数产出。
| |
Item-21 | 必须返回对象时,别妄想返回其reference | C++允许编译器实现者实施最优化用以改善产出码的效率却不改变其可观察的行为。(因此某些情况下,operator*返回值的构造和析构可被安全的消除) | |
Item-22 | 将成员变量声明为private |
| |
Item-23 | 宁以non-member、non-friend替换member函数 | 如果要你在一个member函数(它不只可以访问class内的private数据,也可以取用private函数、enums、typedefs 等等)和一个 non-member,non-friend 函数(它无法访问上述任何东西)之间做抉择,而且两者提供相同机能,那么,导致较大封装性的是 non-member、 non-friend 函数,因为它并不增加“能够访问class内之private成分"的函数数量。 namespace和 classes 不同,前者可跨越多个源码文件而后者不能。 | |
Item-24 | 若所有参数皆需要类型转换,请为此采用non-member函数 |
| |
Item-25 | 考虑写出一个不抛出异常的swap函数 | PIMPL(pointer to implementation):以指针指向一个对象,内含真正数据。 通常我们不能够改变 std 命名空间内的任何东西,但可以为标准 templates (如 swap) 制造特化版本,以使它专属于我们自己的class。 C++ 只允许对 class templates 偏特化,在 function templates 身上偏特化是行不通的。当你打算偏特化一个 function template 时,惯常做法是简单地为它添加一个重载版本。 一般而言,重载 function templates 没有问题,但std 是个特殊的命名空间,其管理规则也比较特殊。客户可以全特化 std 内的 templates, 但不可以添加新的 templates(或 classes或 functions 或其他任何东西)到 std 里头。 如果你想让你的 “class 专属版 “swap 在尽可能多的语境下被调用,你需得同时在class 所在命名空间内写一个 non-member 版本以及一个 std: :swap 特化版本。 如果 swap 缺省实现版的效率不足(那几乎总是意味你的 class或 template使用了某种 pimpl 手法),试着做以下事情: 1. 提供一个 public swap 成员函数,让它高效地置换你的类型的两个对象值,这个函数绝不该抛出异常。 2. 在你的 class 或template 所在的命名空间内提供一个 non-member swap, 并令它调用上述 swap 成员函数。 3. 如果你正编写一个 class (而非 class template) ,为你的 class 特化 std:: swap ,并令它调用你的 swap 成员函数。 4. 调用 swap 时应针对 std:: swap 使用 using 声明式,然后调用 swap 并且不带任何“命名空间资格修饰” 5. 为“用户定义类型”进行 std templates 全特化是好的,但千万不要尝试在std内加入某些对 std 而言全新的东西。 | |
实现 | Item-26 | 尽可能延后变量定义式的出现时间 | 不只应该延后变量的定义,直到非得使用该变量的前一刻为止;甚至应该尝试延后这份定义直到能够给它初值实参为止。 |
Item-27 | 尽量少做转型动作 | 尽量避免转型,特殊是在注重效率的代码中避免dynamic_cast; 如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码; 宁可使用C++ Style(新式)转型,不要使用旧式转型(前者很容易辨识出来)。
dynamic_cast替代方案:使用虚函数:base class 内提供virtual函数做你想对各个派生类想做的事。(通过base class接口处理“所有可能之各种派生类”) | |
Item-28 | 避免返回handles指向对象内部成分 | 避免返回“代表对象内部数据”的handle,将增加“对象的封装性”、帮助const成员函数更像个const,并将发生“dangling handles”的可能性降至最低。 | |
Item-29 | 为“异常安全”而努力是值得的 | 异常安全函数即使发生异常也不会泄露资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证基本型、强烈型、不抛异常型。“强烈保证”往往能通过copy-and-swap实现出来。 copy-and-swap原则:为你打算修改的对象(原件)做出一份副本,然后在那副本身上做一切必要修改。若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换 (swap) | |
Item-30 | 透彻了解inlining的里里外外 | 将大多数inlining限制在小型、被频繁调用的函数身上。 nlining函数:对此函数的每一个调用都以函数本体替换之。
inline是个申请,编译器可以忽略:
将函数声明为inline的冲击:
| |
Item-31 | 将文件间的编译依存关系降至最低 | 支持编译依存最小化的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle Classes【pimpl idiom】和Interface Class。
C++提供关键字export,允许将template声明式和定义式分割于不同的文件内。(不幸的是支持export的编辑器非常少)。 | |
继续与面向对象设计 | Item-32 | 确定你的public继承塑模出is-a关系 | public继承意味着is-a。适用于base class的每一件事情一定也适用于derived class,因为每一个derived class对象也都是一个base class。 |
Item-33 | 避免掩饰继承而来的名称 |
| |
Item-34 | 区分接口继承和实现继承 |
| |
Item-35 | 考虑virtual函数以外的其他选择 | virtual函数替代方案
| |
Item-36 | 绝不重新定义继承而来的non-virtual函数 |
| |
Item-37 | 绝不重新定义继承而来的(virtual)缺省参数值 |
| |
Item-38 | 通过复合塑模出has-a或“根据某物实现出” |
| |
Item-39 | 明智而审慎的使用private继承 | private继承意味implemented-in-terms-of(根据某物实现出)。如果你让Class D以Private形式继承Class B,用意是为了采用class B内已经备妥的某些特性,而不是因为B对象和D对象存在任何观念上的关系。private继承意味只有实现部分被继承,接口部分应略去。 尽可能使用复合,必要时才使用private继承(protected成员或virtual函数牵扯进来时)。 private继承主要用于“当一个意欲成为derived class者想访问一个意欲成为base class者的protected成分,或为了重新定义一个或多个virtual函数”。 C++裁定凡是独立(非附属)对象都必须有非零大小。【EBO:empty base optimization】 | |
Item-40 | 明智而审慎的使用多重继承 |
| |
模板与泛型编程 | Item-41 | 了解隐式接口和编译期多态 | 面向对象编程:总是以显式接口和运行期多态解决问题 Templates及泛型编程:隐式接口和编译期多态 重要性更高 编译期多态:以不同的template参数具现化function templates,会导致调用不同的函数,这就是所谓的编译期多态。 |
Item-42 | 了解typename的双重意义 | template 内出现的名称如果相依于某个 template 参数,称之为从属名称 (dependent names) 。如果从属名称在 class 内呈嵌套状,我们称它为嵌套从属名称 (nested dependent name)。C::const_iterator 就是这样一个名称。实际上它还是个嵌套从属类型名称 (nested dependent type name) ,也就是个嵌套从属名称并且指涉某类型。 C++解析规则:如果解析器在template中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,除非明确告诉它是(关键字typename)。 typename只被用来验明嵌套从属名称;其他名称不应该有它存在。" “typename 必须作为嵌套从属类型名称的前缀词”这一规则的例外:typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization(成员初值列)中作为base class 修饰符。 请使用关键字 typename 标识嵌套从属类型名称;但不得在 base class lists (基类列)或 member initialization list(成员初值列)内以它作为 base class 修饰符。 typename 相关规则在不同的编译器上有不同的实践。这意味 typename和“嵌套从属类型名称"之间的互动,也许会在移植性方面带给你某种温和的头疼。 | |
Item-43 | 学习处理模板化基类内的名称 | C++拒绝在templatized base classes(模板化基类)内寻找继承而来的名称,除非:
| |
Item-44 | 将与参数无关的代码抽离templates | 因非类型模板参数 (non-type template parameters) 而造成的代码膨胀,往往可 消除,做法是以函数参数或 class 成员变最替换 template 参数。 因类型参数 (type parameters) 而造成的代码膨胀,往往可降低,做法是让带有 完全相同二进制表述 (binary representations) 的具现类型 (instantiation types) 共享实现码。 working set:所谓 working set 是指对一个在“虚内存环境”下执行的进程 (process) 而言,其所使用的那一组内存页 (pages)。 | |
Item-45 | 运用成员函数模板接受所有兼容类型 |
| |
Item-46 | 需要类型转换时请为模板定义非成员函数 | function template实参推导过程中并不考虑采纳“通过构造函数而发生的隐式类型转换”。 在一个class template内,template名称可以被用来作为“template和其参数”的简略表达式。 当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“Class template内部的friend函数”。
| |
Item-47 | 请使用traits classes表现类型信息 | traits:允许在编译期间取得某些类型信息。他们以templates和“templates 特化”完成实现。 整合重载技术后(overloading)后,traits classes有可能在编译期间对类型执行if...else测试:
| |
Item-48 | 认识template元编程 |
| |
定制new和delete | Item-49 | 了解new-handler的行为 | 当operator new抛出异常以反映一个未满足的内存需求之前,它会先调用一个客户指定的错误处理函数(new-handler)。为了指定这个用以“处理内存不足”的函数,客户必须调用set new_handler。 new-handler,是当operator new无法满足客户的内存需求时所调用的函数。 一个设计良好的new-handler必须做以下事情:
Nothrow new是一个颇为局限的工具,因为它只适用于内存分配(operator new),后续的构造函数调用还是可能抛出异常。 注意:STL容器所使用的heap内存是由容器所拥有的分配器对象(allocator objects)管理,不是被new和delete直接管理。 |
Item-50 | 了解new和delete的合理替换时机 | operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler; 它也应该有能力处理0byte 申请; Class专属版本则还应该处理比正确大小更大的(错误)申请,错误发生时改用标准operator new。 operator delete应该在收到null指针时不做任何事情; Class专属版本则还应该处理“比正确大小更大的(错误申请)”,错误发生时改用标准operator 非附属/独立式对象:不以“某对象之base class成分”存在的对象。其大小必须非零。 | |
Item-51 | 编写new和delete时需固守常规 | - | |
Item-52 | placement new和placement delete要成对使用 | 如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这便是个所谓的placement new。 伴随placement new调用而触发的构造函数“出现异常时:运行期系统寻找“参数个数和类型都与operator new相同”的某个operator delete,如果找到则调用,否则什么都不做。placement delete只有在“伴随placement new调用而触发的构造函数“出现异常时才会被调用。对一个指针施行delete绝不会导致调用placement delete。
| |
杂项 | Item-53 | 不要轻易忽略编译器的警告 | - |
Item-54 | 让自己熟悉包括RT1在内的标准程序库 | Technical Report 1:
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr,不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。它是一种弱引用。 weak_ptr常用函数:
TR1自身只是一份规范。为了获得TR1提供的好处,你需要一份实物。一个好的实物来源是Boost。 所有Boost组件都位于命名空间boost内,但TR1组件都置于std::tr1内,你可以这样告诉编译器,令它对待references to std::tr1就像对待references to boost一样。 namespace std { namespace tr1=::boost; //定义别名 } | |
Item-55 | 让自己熟悉Boost | - | |
参考博客: