item2: Prefer consts, enums, and inlines to #defines.
reason
- define会造成代码膨胀,目标代码里替换所有define为同一个东西,重复。
- define通常没有scope的概念,也没有封装的概念
- define出来的函数有隐含的出错风险。
conclusion
- 常量:使用const或enum代替
- 函数形式的宏:改用inline函数代替
item3: Use const whenever possible.
reason
- 避免不必要的错误,帮助编译器侦测出错误的用法。对象、函数参数、返回类型、成员函数,都可以用const来修饰。
- 对于member function,const 函数可以使const对象可以调用。
conclusion
- 编译器只保证bitwise constness,程序员应该做到logical constness(const成员函数不应该返回可以修改对象内部状态的引用或指针)
- 当const和non-const版本的函数有等价的实现的时候,让non-const版本调用const版本
item4: Make sure that objects are initialized before they are used.
conclusion
-
使用member initialization list初始化类成员,以声明次数为准。
-
“跨编译单元的初始化次序”问题,把non-local static对象替换为local static对象。使用reference-returning函数。
item7: Declare destructors virtual in polymorphic base class.
reason
- avoid resource leak.
conclusion
- 为polymorphic base class声明一个virtual析构函数。如果base class带有任何的virtual函数,它就应该有一个virtual析构函数
polymorphic base class是指:为实现“要通过base class接口来调用derived class对象”而设计的基类。
- 不是为了继承而定义的base class就不需要声明virtual析构函数,浪费空间(带virtual函数需要该class有vtbl)。并且不要继承这种不带虚析构函数的类(比如stl里面的容器)。
item9: Never call virtual functions during construction or destruction.
reason
- 在base class构造(析构)期间,virtual函数不是virtual函数。
item10: Have assignment operators return a reference to *this.
reason
- 可以实现连锁赋值的效果。非强制,但是这样可以与内置类型和stl保持一致。
item11: Hanle assignment to self in operator=.
conclusion
- 使用证同测试(identity test), (
if (this == &rhs) return *this;
) - 为了异常安全性(exception safety),调整语句执行顺序
- copy-and-swap 技术
item13: Use objects to manage resource
resource 包括内存、文件描述器(file description)、互斥锁、字型和画刷、数据库连接、网络sockets。
reason
- 程序员会容易忘记delete obj;
conclusion
- 使用RAII对象来防止资源泄露
RAII: Resource Acquisition Is Initialization. 获得资源的同时立刻放进管理对象中。 管理对象析构的时候,资源会同时被释放。(如果析构函数抛出异常,参见条款8(不要让异常逃离析构函数)进行处理)
example:
ps: shared_ptr is a RCSP
: reference-counting smart pointer
item14: Think carefully about copying behaviour in resource-managing classes.
conclusion
- 禁止复制:让RAII对象继承noncopyable。
- RCSP: 对底层资源使用引用计数。
- 复制底部资源,深拷贝
- 转移底部资源所有权,如auto_ptr.
常用的是前两种方法
item20: Prefer pass-by-reference-to-const to pass-by-value.
reason
- 节省开销,避免不必要的copy constructor 和 destructor。
- 避免对象切割(slicing)
conclusion
- 尽量使用pass-by-reference-to-const代替pass-by-value,高效,避免slicing。
- pass-by-reference-to-const的方式不适用于:内置类型、stl的迭代器和函数对象,这些类型使用pass-by-value更为适当。
ps: references往往以指针实现,因此pass by reference通常意味着真正传递的是指针。因此内置类型使用pass by value往往比使用pass by reference更为高效。stl的迭代器和函数对象习惯上都被设计为pass by value。
关于封装
如果某些东西被封装,它就不再可见。越多东西被封装,越少人可以看到它。而越少人看到它,我们就有越大的弹性去改变它,因为我们的改变仅仅直接影响看到改变的那些人事物。因此,愈多东西被封装,我们改变那些东西的能力也愈大。这就是我们推崇封装的原因:它使我们能够改变事物而只影响到有限客户。
item22: Declare data members private.
reason
- private == 不可见 == 封装 (除了friend,member function)
- public意味着不封装,不封装意味着不可改变,因为改变了就会直接影响到class的用户。
- protected并不比public的封装性好,如果修改或删掉一个protected的成员,那么所有derived class都会被破坏,因此protected的成员变量也意味着不封装。
conclusion
- 从封装的角度来看,class的访问类型其实只有两种:private == 封装;protected,public == 不封装;
- 应该将成员变量声明为private。这样做,可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者充分的实现弹性。
item23: Prefer non-member non-friend functions to member functions.
reason
-
non-member non-friend functions 不增加private成员的访问次数,意味着更好的封装性(较之于member function和friend function)。 conclusion
-
从封装角度来看,尽量使用non-member non-friend函数替换member函数。这样不破坏封装性,增加包裹弹性和技能扩充性。
为什么non-member non-friend functions的封装性更好呢? 从item22说起,成员变量应该是private的,因为如果不是,就有无限量的函数可以访问它们,它们也就毫无封装性。我们可以这样来衡量封装性:能直接访问(private)数据成员的函数越多,我们就说数据的封装性越低。能够访问private成员的函数只有两种,friend和member函数。而non-member non-friend函数并没有增加private直接被访问的次数,因此说它有更好的封装性。也正因为如此,在firend和member函数,与non-member non-friend函数之间选择,后者更受欢迎。