2017 May 03 编程规范
C++编程规范:编程风格
第14条:宁要编译时和连接时错误,也不要运行时错误
- 编写代码时,应该在编译期间使用编译器检查不变式,而不应该在运行时再进行检查
- 静态检查与数据和控制流无关。静态检查能够提供独立于程序输入和执行流程的保证。相反,要确保运行时检查足够可靠,需要使用对所有输入都具有代表性的用例进行测试
- 静态表示的模型更加可靠
- 静态检查不会带来运行时开销
- 可以使用编译时检查代替运行时检查的情况:
- 编译时布尔条件,如果测试的是编译时的布尔条件,那么可以使用静态断言取代运行时测试
- 编译时多态,在定义泛型函数或者类型时,考虑用编译时多态(模板)代替运行时多态(虚函数)
- 枚举,在需要使用符号常量或首先整数值时考虑定义enum
- 向下强制:如果经常使用dynamic_cast执行向下强制,说明基类提供的功能太少了,此时可以考虑重新设计接口,使程序能够用基类表示计算。
- 例外:有些情况下,无法在编译时检查,必须进行运行时检查,对于这些情况,应该使用断言来检查内部编程错误。
第15条:积极使用const
- 使用mutable成员实现逻辑上的不变,当类的const成员函数需要合法地修改成员变量时,声明该成员变量为mutable的。如果使用Piml惯用法隐藏了所有私有成员,就无需对缓存信息或指向它的为改变的指针声明mutable了。
- 在函数声明中,要避免将通过值传递的函数参数声明为const,但是这种顶级const将对函数的定义产生影响,能够敏感地捕获对参数的无意改变。 void Fun(int x); void Fun(const int x); //重新声明同一函数:顶级const将被忽略
第16条:避免使用宏
- 关于宏的第一条规则:不要使用它,除非不得不用,几乎每个宏都说明程序设计语言、程序或者程序员存在缺陷
- 宏可能展开为在偶然情况下能够编译的“传输线噪声”,宏中的错误可能只有在宏展开之后才能被报告出来,而不是在定义时
- 不要考虑编写一个以常见词或缩略语为名字的宏
- 尽可能快地取消宏的定义(#undef)
- 不要讲宏放在头文件中
- 在条件编译中,要避免在代码中导出杂乱地插入#ifdef
第17条:避免使用“魔数”
- 避免在代码中直接使用文字常量
- 名称能够增加信息,并提供单一的维护点,而程序中到处重复的原始数据时无名的,维护起来很麻烦。
- 常量应该是枚举符或者const值,有合适的作用域和名称
- 可以在类定义中定义静态整数常量,其他类型的常量需要单独的定义或者一个短小的函数
第18条:尽可能局部地声明变量
- 变量的生存期超过必需的长度时会产生以下几个缺点:会使程序更难以理解和维护;名字会污染上下文;不能总是被合理地初始化
- 在能够合理地初始化一个变量之前,绝不要声明它
- 尽可能局部地定义每个变量,通常就是在有了足够的数据进行初始化的时候,而且恰恰就在首次使用变量之前。
- 例外情况:
- 有时候将变量提出循环是有好处的
- 因为常量并不添加状态,所以对常量不适用
第19条:总是初始化变量
- 养成在使用内存内存之前先清除的习惯,可以避免为初始化的变量这种错误,在定义变量的时候就应该将其初始化
- 使用默认初始化值或? :减少数据流和控制流的混合
- 用函数代替复杂的计算流,有时候计算值的最好方式是将计算封装在一个函数中
- 例外:硬件或其他进程直接写入的缓冲区数据和volatile型数据不要程序对其进行初始化。
第20条:避免函数过长,避免嵌套过深
- 尽量紧凑:对一个函数只赋予一种职责
- 不要自我重复:优先使用命名函数,不要让相似的代码片段反复出现
- 优先使用&&:在可以使用&&条件判断的地方要避免使用连续嵌套的if
- 不要过分使用try:优先使用析构函数进行自动清除而避免使用try代码块
- 优先使用标准算法:算法比循环嵌套要少,通常也更好
- 不要根据类型标签进行分支,优先使用多态函数
- 例外情况:如果一个函数的功能无法合理地重构为多个独立的子任务,那么它的较长和嵌套较多就是合理的。
第21条:避免跨编译单元的初始化依赖
- 保持初始化顺序:不同编译单元中的名字空间级对象决不应该在初始化上互相依赖,因为其初始化顺序是未定义的。
- 在不同编译单元中定义两个名字空间级的对象时,先调用哪一个对象的构造函数是没有定义的。因此,在任何名字空间级对象的初始化代码中,不能假设其他编译单元中定义的任何其他对象都已经初始化了。
- 在使用构造函数构造之前,名字空间级对象就已经使用0静态初始化过了。
- 如果确实需要可能以来于另一个对象的此种变量,可以考虑使用单体(Singleton)设计模式。
第22条:尽量减少定义性依赖,避免循环依赖
- 不要过分依赖:如果用前向声明(forward declaration)能够实现,就不要包含(#include)定义
- 不要循环依赖:循环依赖是指两个模块之间或间接地相互依赖,互相依赖的多个模块并不是真正的独立模块,而是紧紧胶着在一起的更高的模块,一个更大的发布单元。
- 除非确实需要类型定义,否则就应该优先使用前向声明,主要在以下两种情况下需要类的完整定义:
- 需要知道类对象的大小时,例如在栈中创建一个对象
- 需要命名或者调用类的成员时
- 为了避免循环依赖,可以使用依赖倒置原理(Dependency Inversion Principle):不要让高层模块依赖于底层模块,而是应该让两者都依赖于抽象。
第23条:头文件应该自给自足
- 应该确保所编写的每个头文件都能够独自进行编译,为此需要包含其内容所依赖的所有头文件。
- 模板是在其定义处编译的,但是非独立的名称或者类型需要等到模板实例化时才编译。 这意味着一个带有std::queue成员的template class Widget即使没有包含deque也不会引发编译期错误。
- 只有在使用时才实例化成员函数模板和模板的成员函数
第24条:总是编写内部#include保护符,绝对不要编写外部#include保护符
- 应该用内部包含保护符保护每个头文件,以避免在多次包含时重新定义
- 定义包含保护符时,应该遵守以下规则:
- 保护符使用唯一名称:确保名称至少应该应用程序中是唯一的
- 不要自作聪明:不要再受保护部分的前后放置代码或者注释,要谨遵上面的标准形式
- 例外情况:在非常罕见的情况下,可能需要多次包含一个头文件。