Effective C++ 50条款
条款 1:尽量用 const 和 inline 而不用#define——尽量用编译器而不用预处理
- #define max(a,b) ((a) > (b) ? (a) : (b))
条款 2:尽量用 < iostream >而不用<stdio.h>
条款 3:尽量用 new 和 delete 而不用 malloc 和 free
- malloc 和 free(及其变体)会产生问题的原因在于它们太简单:他们不知道构造函数和析构函数。
- 把 new 和 delete 与 malloc 和 free 混在一起用也是个坏想法
条款 4:尽量使用 C++风格的注释
- C++中涉及到的内存的管理问题可以归结为两方面:正确地得到它和有效地使用它
条款 5:对应的 new 和 delete 要采用相同的形式
- 用 new 的时候会发生两件事。首先,内存被分配,然后,为被分配的内存调用一个或多个构造函数。用 delete 的时候,也有两件事发生:首先,为将被释放的内存调用一个或多个析构函数,然后,释放内存。
条款 6:析构函数里对指针成员调用 delete
条款 7:预先准备好内存不够的情况
- 当内存分配请求不能满足时,调用你预先指定的一个出错处理函数。这个方法基于一个常规,即当 operator new 不能满足请求时,会在抛出异常之前调用客户指定的一个出错处理函数——一般称为 new-handler 函数
条款 8. 写 operator new 和 operator delete 时要遵循常规
条款 9. 避免隐藏标准形式的 new
- 在类里定义了一个称为“ operator new”的函数后,会不经意地阻止了对标准 new 的访问。一个办法是在类里写一个支持标准 new 调用方式的 operator new,它和标准 new 做同样的事。另一种方法是为每一个增加到 operator new 的参数提供缺省值。
条款 10. 如果写了 operator new 就要同时写 operator delete
条款 11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
- 会存在内存泄漏和重复删除的问题
- 只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。
条款 12: 尽量使用初始化而不要在构造函数里赋值
- 特别是 const 和引用数据成员只能用初始化,不能被赋值
- 用成员初始化列表比在构造函数里赋值要好的一个原因在于效率。
- 对象的创建分两步:a. 数据成员初始化。b. 执行被调用构造函数体内的动作。
- 通过成员初始化列表来进行初始化总是合法的,效率也决不低于在构造函数体内赋值,它只会更高效。另外,它简化了对类的维护,因为如果一个数据成员以后被修改成了必须使用成员初始化列表的某种数据类型,那么,什么也不用变。
- static 类成员永远也不会在类的构造函数初始化。
条款 13: 初始化列表中成员列出的顺序和它们在类中声明的顺序相同
- 类成员是按照它们在类里被声明的顺序进行初始化的,和它们在成员初
始化列表中列出的顺序没一点关系。 - 基类数据成员总是在派生类数据成员之前被初始化
条款 14: 确定基类有虚析构函数
- 当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。
- 如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用
- 实现虚函数需要对象附带一些额外信息,以使对象在运行时可以确定该调用哪个虚函数。对大多数编译器来说,这个额外信息的具体形式是一个称为 vptr(虚函数表指针)的指针。 vptr 指向的是一个称为 vtbl(虚函数表)的函数指针数组。每个有虚函数的类都附带有一个 vtbl。当对一个对象的某个虚函数进行请求调用时,实际被调用的函数是根据指向 vtbl 的 vptr 在 vtbl 里找到相应的函数指针来确定的。
- 无故的声明虚析构函数和永远不去声明一样是错误的。
- 虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。
- 无故的声明虚析构函数和永远不去声明一样是错误的
条款 15: 让 operator=返回*this 的引用
- 总要遵循 operator=输入和返回的都是类对象的引用的原则
条款 16: 在 operator=中对所有数据成员赋值
条款 17: 在 operator=中检查给自己赋值的情况
- 在赋值运算符中要特别注意可能出现别名的情况,其理由基于两点。其中之一是效率。
- 另一个更重要的原因是保证正确性。
条款 18: 争取使类的接口完整并且最小
条款 19: 分清成员函数,非成员函数和友元函数。
- operator>>和 operator<<决不能是成员函数。
条款 20: 避免 public 接口出现数据成员
- 结论是,在 public 接口里放上数据成员无异于自找麻烦,所以要把数据成员安全地隐藏在与功能分离的高墙后
条款 21: 尽可能使用 const
- 使用 const 的好处在于它允许指定一种语意上的约束——某种对象不能被修改——编译器具体来实施这种约束。
- 在一个函数声明中,const 可以指的是函数的返回值,或某个参数;对于成员函数,还可以指的是整个函数
- const 成员函数的目的当然是为了指明哪个成员函数可以在 const 对象上被调用。但很多人忽视了这样一个事实:仅在 const 方面有不同的成员函数可以重载。
条款 22: 尽量用“传引用”而不用“传值”
- 提高效率
- 避免“切割问题”
条款 23: 必须返回一个对象时不要试图返回一个引用
条款 24: 在函数重载和设定参数缺省值间慎重选择
- 如果可以选择一个合适的缺省值并且只是用到一种算法,就使用缺省参数。否则,就使用函数重载。
条款 25: 避免对指针和数字类型重载
条款 26: 当心潜在的二义性
条款 27: 如果不想使用隐式生成的函数就要显式地禁止它
- 显式地声明一个成员函数,就防止了编译器去自动生成它的版本;使函数为 private,就防止了别人去调用它。
条款 28: 划分全局名字空间
条款 29: 避免返回内部数据的句柄
- 这类问题的通用解决方案和前面关于指针的讨论一样:或者使函数为非const,或者重写函数,使之不返回句柄。
条款 30: 避免这样的成员函数:其返回值是指向成员的非 const 指针或引用,但成员的访问级比这个函数要低
条款 31: 千万不要返回局部对象的引用,也不要返回函数内部用 new 初始化的指针的引用
- 写一个返回废弃指针的函数无异于坐等内存泄漏的来临。
条款 32: 尽可能地推迟变量的定义
- 内联函数的基本思想在于将每个函数调用以它的代码体来替换。用不着统计专家出面就可以看出,这种做法很可能会增加整个目标代码的体积。
- 要牢记在心的一条是, inline 指令就象 register,它只是对编译器的一种提示,而不是命令。
- 一个给定的内联函数是否真的被内联取决于所用的编译器的具体实现。
条款 33: 明智地使用内联
条款 34: 将文件间的编译依赖性降至最低
- 句炳类:它只是把所有的函数调用都转移到了对应的主体类中,主体类真正完成工作。
- 协议类:协议类没有实现;它存在的目的是为派生类确定一个接口。所以,它一般没有数据成员,没有构造函数;有一个虚析构函数,还有一套纯虚函数,用于制定接口。
- 句柄类和协议类都不大会使用内联函数。使用任何内联函数时都要访问实现细节,而设计句柄类和协议类的初衷正是为了避免这种情况。
条款 35: 使公有继承体现 “是一个” 的含义
- 公有继承声称:对基类对象适用的任何东西 ---- 任何! ---- 也适用于派生类对象。
条款 36: 区分接口继承和实现继承
条款 37: 决不要重新定义继承而来的非虚函数
条款 38: 决不要重新定义继承而来的缺省参数值
- 对象的静态类型是指你声明的存在于程序代码文本中的类型
- 对象的动态类型是由它当前所指的对象的类型决定的。
- 虚函数是动态绑定的,意思是说,虚函数通过哪个对象被调用,具体被调用的函数就由那个对象的动态类型决定
条款 39: 避免 “向下转换” 继承层次
- 从一个基类指针到一个派生类指针 ---- 被称为 “向下转换”
条款 40: 通过分层(组合)来体现 “有一个” 或 “用…来实现”
条款 41: 区分继承和模板
条款 42: 明智地使用私有继承
- 私有继承意味着只是继承实现,接口会被忽略。
- 尽可能地使用分层,必须时才使用私有继承
- 模板导致的 “代码膨胀”
- 基于私有继承的实现避免了代码重复
- C++的各种特性是以非凡的方式相互作用的
条款 43: 明智地使用多继承
- 二义性问题——定义新类来解决
- 基类虚拟继承问题
条款 44: 说你想说的;理解你所说的
- 公有继承意味着 “是一个”
- 私有继承意味着 “用…来实现”
- 分层意味着 “有一个” 或 “用…来实现”
- 纯虚函数意味着仅仅继承函数的接口
- 简单虚函数意味着继承函数的接口加上一个缺省实现
- 非虚函数意味着继承函数的接口加上一个强制实现