《Effective C++》阅读笔记-让自己习惯C++:1-4

本文最后更新于:2021年7月10日 下午

《Effective C++》阅读笔记 Part1让自己习惯C++:1-4

C++经典读物,网上也有很多笔记可供参考,我除了记录书中给出的结论,还加入了自己的理解和一些额外的笔记,有些条款没看懂的就先空着,如果发现我有抄书行为那就是我偷懒了orz

一、让自己习惯C++

01 / 视 C++ 为一个语言联邦

C并不是一个带有一组守则的一体语言;它是从四个次语言(C、Object-Oriented C、Template C++、STL)组成的联邦政府,每个次语言都有自己的规约。C高效编程守则视状况而变化,取决于你使用C的哪一部分。

02 / 尽量以 const,enum,inline 替换 #define

  • 对于单纯常量,最好以const对象或enums替换 #define。

  • 对于形似函数的宏(macros),最好改用inline函数替换 #define。

意思就是,尽量用编译器的工作换掉预处理的工作。

编译过程:.c源代码文件 – 预处理(宏替换等操作) --> .i文件 – 编译器 --> .s文件(汇编代码)–> .o对象文件 – 链接 --> bin文件

详见C/C++编译链接过程

单纯常量用const、enum替代

#define => const [type] [variable]

用 #define 来声明常量时,由于 #define 往往不是语言的一部分,记号名称可能不会被记入记号表导致不方便 debug 排查问题,最好替换成一个常量。

  1. 对于定义常量指针,最好使用string对象而非char*;

  2. 对于class专属常量,通常为了限制作用域而让常量成为class成员,确保实体唯一则需要给常量声明为static成员static const [type] [variable];,声明后还要定义const [type] [class]::[variable]=[value];。( 万一编译器不允许这样完成类内初值设定,可以通过enum类型予以补偿)

#define => enum { [variable] = [value]}

“the enum hack” 用法

class GamePlayer {
private:
    enum { NumTurns = 5 };
    //令NumTurns成为5的记号名
    int scores[NumTurns];
    ...
}

对于class这部分,#define的劣势就是:

无法利用 #define 创建一个 class 专属常量,因为 #define 并不重视作用域(scope)。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被 #undef)。这意味 #define 不仅不能够用来定义 class 专属常量,也不能够提供任何封装性,也就是说没有所谓 private #define 这样的东西。

函数#define用模板inline代替

用宏实现函数时,记得为宏中的所有实参加括号,否则在表达式中调用这个宏时可能会遇到麻烦,但即使这么做,仍存在风险。如果写出template inline函数(见条款30),则可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性。

#define => template inline

范例如下:

//调用a和b中的较大值
#define CALL_WITH_MAXC(a, b) f((a) > (b) ? (a) : (b))

应改为:

template<typename T>
inline void callWithMax(const T& a, const T& b)
{
    f(a > b ? a : b);
}
//由于T未知所以传const引用,见条款20

03 / 尽可能的使用 const

  • 将某些东西声明为 const 可帮助编译器侦测出错误用法。const 可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
  • 编译器强制实施 bitwise constness(又称physical constness),但你编写程序时应该使用“概念上的常量性”(conceptual constness)。
  • 当 const 和 non-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可避免代码重复。
char const *p = greeting;
const char* p = greeting; //如果const在星号左边,不论类型与const前后,表示被指物是常量;
char* const p = greeting; //如果const在星号右边,表示指针自身是常量;
const char* const p = greeting; //如果const出现在星号两边,表示被指物和指针两者都是常量。

令函数返回常量值,避免错误行为。

mutable关键字

04 / 确保对象被使用前已先被初始化

  • 为内置型对象进行手工初始化,因为C++不保证初始化它们。
  • 构造函数最好使用成员初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
  • 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-localstatic对象。