C++编程规范 编程风格

来源:互联网 发布:山西 软件开发水平 编辑:程序博客网 时间:2024/06/10 20:34

第14条 宁要编译时和连接时错误,也不要运行时错误
能够在编译时做的事情,就不要推迟到运行时:编写代码时,应该在编译期间使用编译器检查不变式(invariant),而不应该在运行时再进行检查。运行时检查取决于控制流和数据的具体情况,这意味着很难知道检查是否彻底。相比而言,编译时检查与控制流和数据无关,一般情况下能够获得更高的可信度。
详细:
1、静态检查与数据和控制流无关。
2、静态表示的模型更加可靠。
3、静态检查不会带来运行时开销。
4、有些情况下,可以用编译时检查代替运行时检查:编译时布尔条件;编译时多态(模板);枚举;向下强制(downcast)。

第15条 积极使用 const
const 是我们的朋友:不变的值更易于理解、跟踪和分析,所以应该尽可能地使用常量代替变量,定义值的时候,应该把 const 作为默认的选项:常量很安全,在编译时会对其进行检查(见 C14),而且它与 C++ 的类型系统已浑然一体。不要强制转换 const 的类型,除非要调用常量不正确的函数(见 C94)。
详细:
1、请注意 const 并不深。
2、用 mutable 成员实现逻辑上的不变。
3、是的,const 有“病毒性”——即使只在一处加入,当你调用其他签名不是常量正确的函数时,它也会传播到代码各处。
4、常量正确性是值得实现的,它已经得到证实而且非常有效,应该大力推荐。
5、不要强制转换 const,除非要调用常量不正确的函数,或者在一些很罕见的情况下,为了解决老编译器中不支持 mutable 的问题。

第16条 避免使用宏
实不相瞒:宏是 C 和 C++ 语言的抽象设施中最生硬的工具,它是披着函数外衣的饥饿的狼,很难驯服,它会我行我素地游走于各处。要避免使用宏。
详细:
1、在 C++ 中几乎从不需要用宏。可以用 const 或者 enum 定义易于理解的常量(见 C15),用 inline 避免函数调用的开销(但是要见 C8),用 template 指定函数系列和类型系列(见 C64~C67),用 namespace 避免名称冲突(见 C57~C59)。
2、关于宏的第一规则就是:不要使用它,除非不得不用。
3、即使在极少的情况下,有正当理由编写宏,也绝不要考虑编写一个以常见词或者缩略语为名字的宏。尽可能快地取消宏的定义(#undef),总是给它们取形如 SCREAMING_UPPERCASE_AND_UGLY 这样明显的、大写的、而且难看的名字,并且不要将它们放在头文件中。
4、宏仍然是几个重要任务的唯一解决方案,比如 #include 保护符(guard)(见 C24),条件编译中的 #ifdef 和 #if defined,以及 assert 的实现(见 C68)。
5、在条件编译(如与系统有关的部分)中,要避免在代码中到处杂乱地插入 #ifdef。相反,应该对代码进行组织,利用宏来驱动一个公共接口的多个实现,然后始终使用该接口。

第17条 避免使用“魔数”
程序设计并非魔术,所以不要故弄玄虚:要避免在代码中使用诸如 42 和 3.14159 这样的文字常量。它们本身没有提供任何说明,并且因为增加了难于检测的重复而使维护更加复杂。可以用符号名称和表达式替换它们,比如 width * aspectRatio。
详细:
1、常量应该是枚举符或者 const 值,有合适的作用域和名称。
2、应该用符号常量替换直接写死的字符串。
3、重要的特定于领域的常量应该放在名字空间一级(const size_t PAGE_SIZE = 4096;)。
4、特定于类的常量可以在类定义中定义静态常量(static const int defaultWidth = 400;)。

第18条 尽可能局部地声明变量
避免作用域膨胀,对于需求如此,对于变量也是如此。变量将引入状态,而我们应该尽可能少地处理状态,变量的生存期也是越短越好。这是 C10 的一个特例,但值得单独阐述。
详细:
1、变量的生存期超过必须的长度时会产生以下几个缺点:它们会使程序更难以理解和维护;它们的名字会污染上下文;它们不能总是被合理地初始化。
2、尽可能局部地定义每个变量,通常就是在你有了足够的数据进行初始化的时候,而且恰恰就在首次使用变量之前。
3、有时候将变量提出循环是有好处的(见 C9);本条对常量不适用。

第19条 总是初始化变量
一切从白纸开始:未初始化的变量是 C 和 C++ 程序中错误的常见来源。养成在使用内存之前清除的习惯,可以避免这种错误,在定义变量的时候就将其初始化。
详细:
1、使用默认初始值或 ?: 减少数据流和控制流的混合。
2、用函数替代复杂的计算流。
3、初始化数组(char path[MAX_PATH] = { '\0' };)。一般而言安全性总是优于不必要的效率考虑。

第20条 避免函数过长,避免嵌套过深
短胜于长,平优于深:过长的函数和嵌套过深的代码块的出现,经常是因为没能赋予一个函数以一个紧凑的职责所致(见 C5),这两种情况通常都能够通过更好的重构予以解决。
详细:
1、尽量紧凑:对一个函数只赋予一种职责(见 C5)。
2、不要自我复制:优先使用命名函数,而不要让相似的代码片段反复出现。
3、优先使用 && :在可以使用 && 条件判断的地方要避免使用连续嵌套的 if 。
4、不要过分使用 try :优先使用析构函数进行自动清除而避免使用 try 代码块(见 C13)。
5、优先使用标准算法:算法比循环嵌套要少,通常也更好(见 C84)。
6、不要根据类型标签(type tag)进行分支(switch)。优先使用多态函数(见 C90)。

第21条 避免跨编译单元的初始化依赖
保持(初始化)顺序:不同编译单元中的名字空间级对象决不应该在初始化上互相依赖,因为其初始化顺序是未定义的。这样做会惹出很多麻烦,轻则在项目中稍做修改就会引发奇怪的崩溃,重则出现严重的不可移植问题——即使是同一编译器的新版也不行。
详细:
1、应该尽可能地避免使用名字空间级的变量,它们很危险(见 C10)。
2、可以考虑使用 Singleton(单体)设计模式。Singleton 本质上也是全局变量——披着羊皮的“狼”(见 C10),它会因为相互依赖或者循环依赖而被破坏。

第22条 尽量减少定义性依赖。避免循环依赖
不要过分依赖:如果用前向声明(forward declaration)能够实现,那么就不要包含(#include)定义。
不要互相依赖:循环依赖是指两个模块直接或者间接地互相依赖。所谓模块就是一个紧凑的发布单元(见“名字空间与模块”部分的引言部分)。互相依赖的多个模块并不是真正的独立模块,而是紧紧胶着在一起的一个更大的模块,一个更大的发布单元。因此,循环依赖有碍于模块性,是大型项目的祸根。请避免循环依赖。
详细:
1、优先使用前向声明,除非:需要知道类对象的大小;需要命名或者调用类的成员。
2、通过依赖倒置使高层模块不依赖于低层模块,而是让两者都依赖于抽象,来打破循环依赖。
3、模块内部的互相依赖不一定是坏事,例如 Command 和 Visitor 等设计模式,如果需要打破这种相互依赖,则需要进行明确的设计,例如 Acyclic Visitor(非循环访问器)设计模式。

第23条 头文件应该自给自足
各司其责:应该确保所编写的每个头文件都能够独自进行编译,为此需要包含其内容所依赖的所有头文件。
详细:
1、头文件应该包含需要的头文件,但是不要包含并不需要的头文件。
2、非独立名称,也应该包含相关的头文件。
3、只在使用时才实例化成员函数模板和模板的成员函数,也应该包含相关的头文件。

第24条 总是编写内部 #include 保护符,决不要编写外部 #include 保护符
为头(文件)添加保护:在所有头文件中使用带有唯一名称的包含保护符(#include guard)防止无意的多次包含。
详细:
1、可以类似如下:

#ifndef PROJECT_NAME_PATH_FILE_NAME_H_#define PROJECT_NAME_PATH_FILE_NAME_H_// ...... ProjectName 项目中 Path 目录下 FileName.h 文件的内容 ......#endif
2、保护符使用唯一名称,可以包含应用程序名称。
3、不要自作聪明,不要在受保护部分的前后放置代码或在注释。

返回 目录

返回《C++ 编程规范及惯用法》

原创粉丝点击