导读
1. C++对 signature 的定义并不包含函数的返回类型。
2. 将构造函数声明为explicit避免隐式转换
3. 当新对象被定义时,一定会有构造函数被调用,因此Widget w3 = w2; 会调用Widget的copy构造函数,而不是assignment操作符。
4. 可以使用operator * (a, b); 来实现相乘的操作,其他操作符也类似。
1.Accustoming Yourself to C++
Item 1:View C++ as a federation of languages.
1. 内置类型的值传递(pass by value)比引用(pass by reference)更高效
2. C++可以认为是由 C , OO, Template和 STL组成
Item 2: Prefer consts,enums,and inlines to #defines.
1. 定义一个常量字符串,const char* const str = "a string";
2. class专属常量:如static const int num = 5; 这是一个声明,需要在类外提供定义const int ClassName::num = 5; (除非这个类型是整数类型,如int char bool, 也有可能编译不支持省略定义)。
3. 用template inline 函数代替 函数宏。
Item 3: Use const whenever possible
1. const char* const p; 第一个const (*号左边的,可以放在类型后边)修饰data, 第二个修饰pointer.
2. STL中,const std::vecotr<int>::iterator iter;代表迭代器不可变,而std::vector<int>::const_iterator cIter代表所指的东西不可变。
3. 对函数的返回值(引用类型)加const使返回的引用不被修改, 这样的函数可以重载没有const的函数。当一个const对象调用这个函数时,会调用带const的那个重载函数。
4. const成员函数不可以改变对象内任何no-static成员变量,除非对变量加上mutable修饰符。
5. 当const 和 non-const 成员函数实现相同时,让non-const 调用const(使用cast) 来避免代码重复。
Item 4: Make sure that objects are initialized before they’re used.
1. C++规定,对象的成员变量的初始化应发生在进入构造函数本体之前,在构造函数体内的不叫初始化,而叫赋值。
2. 对于内置类型的初始化,应使用初始化列表。
3. 如果不在进入构造函数本体之前进行初始化,而直接在构造函数体内进行赋值,则会调用一次成员变量的默认构造函数和一次拷贝构造函数,造成了浪费。
4. 初始化顺序:父类早于子类。类成员按声明顺序初始化(即使初始化列表的顺序不一样,但应尽量保持和声明一样的顺序)。
5. 函数内的static变量称为local static,其它区域的static变量称为non-local static
6. 不同编译单元(生成一个目标文件的代码)的non-local static变量的初始顺序不确定,当有相互引用时,应使用Singleton模式。
7. 任何一种non-const的static对象,在多线程环境下等待某事发生都会有麻烦。一种处理办法是在单线程启动阶段,手工调用。(类似的,ios中单例模式,使用dispatch_once来保证)
Constructors, Destructors, and Assignment Operators
Itme 5: Know what functions C++ silently writes and calls.
1. 编译器默认为一个class生成(如果被调用的话,后同)copy构造函数、copy assignment 操作符和一个析构函数。如果没有定义其它构造函数,还会生成一个default构造函数。这些函数都是public 和 inline。
2. c++不允许reference 改指向不同的对象。
3. 生成copy构造函数和copy assignment 时,只是单纯的将non-static对象拷贝。如果拷贝不合法,则编译器不会生成这个函数。
Item 6: Explicitly disallow the use of compiler-generated functions you do not want.
1. 为驳回上一条中编译器生的函数,可将其声明为private并且不予实现。(c++11中使用 = delete)
Item 7: Declare destructors virtual in polymerphic base classes.
1. 为多态的基类声明virtual析构函数。
Item 8: Prevent exceptions from leaving destructors.
1. 不要在析构函数里抛异常。
2. 如果需要对某个操作函数运行期间抛出异常作出反应,那么该函数的所在的class应该提供一个普通函数来执行该操作,而不是在这个class的析构函数中。
Item 9: Never call virtual functions during construction or destruction.
1. 因为父类先于派生类构造,所以父类构造期间virtual 不会起作用。
2. 令derived classes 将必要的构造信息传递给base clase 构造函数可以弥补9.1无法实现的功能。
Item 10: Have assignment operators return a reference to *this.
1. 让operator= 等(还有+=,-=等)操作符重载函数返回 *this的引用,使可以写成x = y = z这样的形式。
Item 11: Handle assignment to self in operator=.
1. 通过判断是否相等处理自我赋值(会有一些性能损失)。
2. 如果不使用1中的方法,在写operator=时要考虑如果出现自我赋值,会不会导致数据丢失。
3. 使用swap.
Item 12: Copy all parts of an object.
1. Copying 函数(指copy 和 copy assignment)应确保复制所以成员变量和父类成员变量,后者通过调用父类的Copying函数来完成。
2. copy 和 copy assignment 有重复代码也不应该相互调用,应新建一个private函数来给两者调用,通常命名为init.
Resource Management
Item 13: Use objects to manage resources.
1. STL中的auto_ptr就是使用的这个思想,常被称为“资源取得时机便是初始化时机”(Resource Acquisition Is Initialization;RAII),RAII可理解为:“资源在构造期获得,在析构期释放”。
2. 使用管理对象的析构函数来确保其拥有的资源被释放。
3. 当auto_ptr被copy时,将变成null,新的指针将取得资源的所有权。
4. auto_ptr不用于动态分配的数组,因为它的析构函数里做的是delete而不是delete []
5. C++11中不再使用auto_ptr.
Item 14: Think carefully about copying behavior in resource-managing classes.
处理RAII对象的拷贝行为:
1. 禁止拷贝(Item 6中有说)。
2. 使用shared_ptr管理拥有的资源,如果“释放“动作并不是删除,可以为shared_ptr指定删除器.
3. 深度拷贝。
4. 转移所有权(像auto_ptr那样)。
Item 15: Provide access to raw resources in resource-managing classes.
1. auto_ptr 和 shared_ptr 都可以通过get获取管理的资源的指针。
2. 可使用隐式转换函数operator OtherClass() const; 获取其底部资源。(容易出错,需具体分析)
Item 16: Use the same form in corresponding uses of new and delete.
1. 使用new[] 后,应使用 delete[]来释放。
2. 不要对数组形式做typedef动作。
Item 17: Storre newd objects in smart pointers in standalone statements.
1. 将初始化智能指针写成独立语句。(不过似乎没有不写成独立语句的理由)
4. Designs and Declarations
Item 18: Make interfaces easy to use correctly and hard to use incorrectly.
1. 应将类的接口设计为不容易被错误调用的形式,
2. 使创建资源接口返回一个shared_ptr,强迫调用者使用shared_ptr管理这个指针。
3. 为防止调用者使用delete删除2中返回的指针,应为指针绑定删除器。
Item 19: Treat class design as type design.
1. Class的设计就是type的设计,应当考虑如下几个(一部分)问题:
2. - 新type的对象如何创建和销毁
3. - 什么是type的合法值
4. - 需要什么转换
5. - 是否需要重载操作符
6. - 是否使用模板
Item 20: Prefer pass-by-reference-to-const to pass-by-value.
1. 函数参数为值传递方式时,调用的是copy构造函数。
2. 引用方式实际实现是指针,所以当派生被作为一个基类引用传递时,也不会被切割。
3. 适合用值传递的类型:内置类型,STL的迭代器和函数对象。
Item 21: Don’t try to return a reference when you must return an object.
1. 不要返回一个局部变量的引用或指针。
Item 22: Declare data members private.
1. 将成员变量声明为private,对外提供读写函数。
Item 23: Prefer non-member non-frend functions to member functions.
1. 将类的一组操作写到非成员函数里边,并把这些函数放到和类相同的命名空间中,并将不同类型的操作放到不同的头文件中。这样做增加了类的封装性和可扩充性,同时可以降低编译的依存性。
Item 24: Declare non-member functions when type conversions should apply to all parameters.
1. 重载一个类A的乘操作符时,与类A相乘的是其他类型B,B可以隐式转换成A,如果需要写成A*B的形式,则需要将operator*写成非成员函数。
2. 成员函数的反面应该是非成员函数,而不是friend。
Item 25: Consider support for a non-throwing swap.
1. 可以对类提供一个特化的swap函数来提高效率,一般针对”以指针指向一个对象,内含真正的数据(pimpl)“类型。
Implementations
Item 26: Postpone variable definitions as long as possible
1. 过早的定义变量,如果在使用变量之前抛出异常,则浪费了一次构造和析构的时间。
2. 在循环里使用的变量,应该定义在循环里,除非赋值操作的成本低于构造+析构,并且效率敏感。
Item 27: Minimize casting.
1. 使用括号的方式是旧式转型,在C++应使用cast
2. 但还是要尽量避免使用cast, 尤其dynamic_cast。
Item 28: Avoid returning “handles” to object internals.
1. 避免返回handle 指向对象的内部成份,一旦这样做就导致handle比其所指对象更长寿,有可能造成悬空。
Item 29: Strive for exception-safe code.
1. 异常安全性函数的要求:不管什么时候抛出异常,都保证不泄漏任何资源、不允许数据败坏。
2. 在函数后面加throw() 指定异常的类型,当不是指定类型的异常时,会调用 unexpected handler. 这些特性在c++11中弃用但仍然支持。
3. 函数的异常安全程度:基本保证(数据不败坏)、强烈保证(在抛出异常时恢复操作前的状态)、不拋异常。
4. 使用copy-and-swap来提供强烈保证,既修改再成功后再一起替换。
Item 30: Understand the ins and outs of inlining.
1. 编译器的优化机制通常被设计用来优化那些”不含函数调用“的代码。
2. 太复杂的函数和virtual函数的inline申请会落空。
3. 函数指针调用会忽略inline声明。
4. inline函数可能会无法调试。
5. 谨慎使用inline函数,限制在小型、被频繁调用的函数身上。
Item 31: Minimize compilation dependencies between files.
1. 使用class的声明式class xxx;而不是#include 可以降低文件的依存性。
2. 考虑Handle classes 和 Interface clases。(不太理解其优点)
Item 32: Make sure public inheritance models “is-a”.
1. public inheritance 意味着“is-a”的关系。
2. 防止无效的代码通过编译:在编译期拒绝“企鹅飞行”的设计。
Item 33: Avoid hiding inherited names.
1. 局部变量只要名称和全局变量相同就会掩盖,不管类型一不一样。
2. 派生类的函数会覆盖基类中所有同名的函数,与类型无关,但可以使用using BaseClass::func;使用它,让基类的所有同名函数在派生类都可以见。
Item 34: Differentiate between inheritance of interface and inheritance of implementation.
1. pure virtual 是可以提供定义的,但调用时要指出其class名称。可以用来为virtual函数提供缺省实现。
2. 声明pure virtual 函数的目的是为了让derived classes 只继承函数接口。
3. 声明simple virtual函数的目的,是让derived classes 继承该函数的接口和缺省实现。
4. 声明non-virtual函数的目的是为了令derivaed classes 继承函数的接口及一份强制性实现(不应该被重新定义)。
Item 35: Consider alternatives to virtual functions.
1. 使用NVI和Strategy手法来替代virtual。(Strategy 的具体优点不太理解)
2. C++中允许private为virtual,JAVA中禁止。
Item 36: Never redefine an inherited non-virtual functions.
1. 不重新定义继承而来的non-virtual函数。(似乎34已经说明了这个问题)
Item 37: Never redefine a function’s inherited default parameter value.
1. 当一个virtual函数有默认参数值时,继承它时,不能更改其默认参数值。因为默认参数值是静态绑定的,也就是会一只使用base class 的默认参数。一个替代方案就是使用35的NVI手法。
Item 38: Model “has-a” or “is-implemented-in-terms-of” through composition.
1. 软件处理的对象分为两个领域,应用域:对应世界中事物,如人、汽车等。实现域:实现细节上的人工制品,如缓冲区、互斥器等。
2. 应用域使用has-a关系,实现域则是is-impl-in-terms-of (185页).
Item 39: Use private inheritance judiciously.
1. 使用protect 和 private继承时,不会自动将一个derived class 对象转换成base class对象。
2. private继承意味着复合中的is-impl-in-terms-of,只是一种实现,在设计上没有特殊含义。并且应尽量使用复合来实现这种关系,除非和protect或private成员扯上关系时。
3. 特殊情况:当base class不占用空间时,而derived class 又对空间敏感,且是单继承时,应使用private继承实现is-impl-in-terms-of 关系。
Item 40: Use multiple inheritance judiciously.
1. 当多继承的base classes 又同时继承自一个base class时,既A继承自B和C,B和C继承自D, 默认情况下,A拥有两份D的数据成员。如果B和C使用virtual继承D,则A不会有两份D的数据成员。
2. virtual base class应该尽量不带数据。
3. 多重继承应用于:public继承一个interface class ,private继承某个协助实现的class