一、头文件
1. #define的保护:所有头文件都应该使用#define防止头文件被多重包含(multiple inclusion),命名格式:
<PROJECT>_<PATH>_<FILE>_H_
为保证唯一性,头文件的命名应基于其所在项目源代码树的全路径。
2.头文件依赖:使用前置声明(forward declarations)尽量减少.h文件中#include的数量。避免多米诺骨牌效应
e.g.头文件中用到类File,但不需要访问File的声明,则头文件只需前置声明class File;无需#include "file/bash/file.h"。
在头文件中如何做到使用类Foo而无需访问类的定义?
1)将数据成员类型声明为Foo* 或Foo &;
2)参数、返回值类型为Foo的函数只是声明(但不定义实现);
3)静态数据成员的类型可以被声明为Foo,因为静态数据成员的定义在类定义之外。
有时,使用指针成员替代对象成员的确更有意义,然而,这样的做法会降低代码可读性及执行效率。如果仅仅为了少包含头文件,还是不要这样代替的好。
3.内联函数:只有当函数只有10行甚至更少时才将其定义为内联函数。对于析构函数应慎重对待,析构函数往往比其表面看起来要长,因为有一些隐式成员和基类析构函数(如果有的话)被调用!另外内联那些包含循环或switch语句的函数是得不偿失的,除非在大多数情况下,这些循环语言从不执行。复杂的内联函数的定义,应放在后缀为-inl.h的头文件中。
4.函数参数的顺序:输入参数在前,输出参数在后(输入和输出)。输入参数使用值传递或者常数引用传递,输出参数使用非常数指针传递。
5.包含文件的名称和次序:C库、C++库、其他库的.h、项目内的.h。避免隐藏依赖。
二、作用域
1.命名空间:在.cc文件中,提倡使用不具名的命名空间,使用具名命名空间时,其名称可基于项目或路径名称,不需使用using指示符。
2.嵌套类:当公开嵌套类作为接口的一部分时,虽然可以直接将他们保持在全局作用域中,但将嵌套类的声明置于命名空间时更好的选择。只能在被嵌套类的定义中才能前置声明嵌套类。不要将嵌套类定义为public,除非它们是接口的一部分。
3.非成员函数、静态成员函数和全局函数:使用命名空间中的非成员函数或静态成员函数,尽量不要使用全局函数。
1)非成员函数不应依赖于外部变量,并尽量置于某个命名空间中。相比单纯为了封装若干不共享任何静态数据的静态成员函数而创建类,不如使用命名空间。
2)定义于同一编译单元的函数,被其他编译单元直接调用可能会引入不必要的耦合和连接依赖;静态成员函数对此尤其敏感,可以考虑提取到新类中,或者将函数置于独立库的命名空间中。
3)如果确实需要定义非成员函数,又只是在.cc文件中使用它,可使用不具备命名空间或static关联限制其作用域。
4.局部变量:将函数变量尽可能置于最小作用域内,在声明变量时将其初始化。
5.全局变量:class类型的全局变量时被禁止的,内建类型的全局变量是允许的,当然多线程代码中非常数全局变量也是被禁止的,永远不要使用函数返回值初始化全局变量。
1)如果一定要使用class类型的全局变量,请使用单件模式
2)对于全局的字符串常量,使用C风格的字符串,而不要使用STL的字符串;
3)静态成员变量视作全局变量,所以,也不能是class类型!
总结:作用域的使用,除了考虑名称污染、可读性之外,主要是为降低耦合度,提高编译、执行效率。
三、类
1.不在构造函数中做太多逻辑相关的初始化,可能的话使用Init()方法集中初始化有意义的数据。
在构造函数中执行操作引起的问题有:
1)构造函数中不易报告错误,不能使用异常。
2)操作失败会造成对象初始化失败,引起不确定状态。
3)构造函数内调用虚函数,调用不会派发到子类实现中,即使当前没有子类化实现,将来仍是隐患。
4)如果有人创建该类型的全局变量(虽然违背了上节提到的规则),构造函数将在main()之前被调用,有可能破坏构造函数中暗含的假设条件。
2.编译器提供的默认构造函数不会对变量进行初始化,如果定义了其他构造函数,编译器不再提供,需要编码者自行提供默认构造函数;
3.为避免隐式转换,需将单参数构造函数声明为explicit(明确的);例外,在少数情况下,拷贝构造函数可以不声明为explicit;特意作为其他类的透明包装器的类。
4.为避免拷贝构造函数,赋值操作的滥用和编译器自动生成,可目前声明其为private且无需实现;仅在代码中需要拷贝一个类对象的时候使用拷贝构造函数,不需要拷贝时应使用DISALLOW_COPY_AND_ASSIGN。
5.仅在作为数据集合时使用struct;
6.组合>实现继承>接口继承>私有继承,子类重载的虚函数也要声明virtual关键字,虽然编译器允许不这样做;
7.避免使用多重继承,使用时,除一个基类含有实现外,其他积累均为纯接口;
8.接口类类名为Interface为后缀,除提供带实现的虚析构函数、静态成员函数外,其他均为纯虚函数,不定义非静态数据成员,不提供构造函数,提供的话,声明为protected;
9.为降低复杂性,尽量不重复操作符,模板、标准类中使用时提供文档说明;
10.存取函数一般内联在头文件中;
11.声明次序:public->protected->private;
12.函数体尽量短小、紧凑、功能单一。
四、C++特性
1.对于智能指针,安全第一、方便第二,尽可能局部化;
2.引用形参加上const,否则使用指针形参;
3.函数重载的使用要清晰、易读;
4.鉴于容易误用,禁止使用缺省函数参数(值得商榷);
5.禁止使用变长数组;
6.合理使用友元;
7.为了方便代码管理,禁止使用异常(值得商榷);
8.禁止使用RTTI,否则重新设计代码;
9.使用c++风格的类型转换,除单元测试外不要使用dynami_cast;
10.使用流还printf+read/write,it is a problem;
11.能用前置自增/减,不用后置自增/减;
12.const能用则用,提倡const在前;
13.使用确定大小的类型,除位组外不要使用无符号型;
14.格式化输出及结构对齐时,注意32位和64位的系统差异;
15.除字符串化、连接外尽量避免使用宏;
16.整数用0,实数用0.0,指针用NULL,字符(串)用‘\0‘;
17.用sizeof(varname)代替sizeof(type);
18.只使用Boost中被认可的库。
五、命名规则
1.总体规则:不要随意缩写。除函数名可适当为动词外,其他命名尽量使用清晰易懂的名词;
2.宏、枚举等使用全部大写+下划线;
3.变量(含类、结构体成员变量)、文件、命名空间、存取函数等使用全部小写+下划线,类成员变量以下划线结尾,全局变量以g_开头;
4.普通函数、类型(含类与结构体、枚举类型)、常量等使用大小写混合,不含下划线;
5.参考现有或相近命名约定。
六、注释
1.关于注释风格,很多c++程序员更喜欢行注释,c程序员或者对块注释依然情有独钟,或者在文件头大段大段的注释时使用块注释;
2.TODO;
3.适当的缩进;
七、格式
1.行宽原则上不超过80列;
2.尽量不使用非ASCII字符,如果使用的话,参考UTF-8格式(尤其是UNIX/LINUX下,Windows下可以考虑宽字符),尽量不将字符串常量耦合到代码中,比如独立出资源文件,这不仅仅是风格问题了;
3.UNIX/LINUX下无条件使用空格,MSVC的话使用Tab也无可厚非;
4.函数参数、逻辑条件、初始化列表;要么所有参数和函数名放在同一行,要么所有参数并排分行;
5.除函数定义的左大括号可以置于行首外,包括函数/类/结构体/枚举声明、各种语句的左大括号置于行尾,所有右大括号独立成行;
6../->操作符前后不留空格,*/&不要前后都留,一个就可,靠左靠右依各人喜好;
7.预处理指令/命名空间不适用额外缩进,类/结构体/枚举/函数/语句使用缩进;
8.初始化用=还是()依个人喜好,统一就好;
9.return不要加();
10.水平/垂直留白不要滥用,怎么易读怎么来。
八、规则之例外
1.windows代码
1)不要使用匈牙利命名法,使用Google命名规则,包括对源文件使用.cc扩展名;
2)Windows定义了很多原有内建类型的同义词,如DWORD、HANDLE等等,在调用Windows API时这是完全可以接受甚至鼓励的,但还是尽量使用原来的C++类型,例如,使用const TCHAR *而不是LPCTSTR;
3)使用Microsoft Visual C++进行编译时,将警告级别设置为3或更高,并将所有warnings当做errors处理;
4)不要使用#pragma once;作为包含保护,使用C++标准包含保护,包含保护的文件路径包含到项目树顶层;
5)除非万不得已,否则不使用任何不标准的扩展,如#pragma和_declspec,允许使用_ _declspec(dllimport)和_ _declspec(dllexport),但必须通过DLLIMPORT和DLLEXPORT等宏,以便其他人在共享使用这些代码时容易放弃这些扩展。
在windows上,只有很少一些偶尔可以不遵守的规则:
1)通常我们禁止使用多重继承,但在使用COM和ATL/WTL类时可以使用多重继承,为了执行COM或ATL/WTL类及其接口时可以使用多重实现继承;
2)虽然代码中不应使用异常,但在ATL和部分STLl(包括Visula C++的STL)中异常被广泛使用,使用ATL时,应定义_ATL_NO_EXCEPTIONS以屏蔽异常,你要研究一下是否也屏蔽掉STL的异常,如果不屏蔽,开启编译器异常也可以,注意这只是为了编译STL,自己仍然不要写含异常处理的代码;
3)通常每个项目的每个源文件中都包含一个名为StdAfx.h或precompile.h的头文件方便头文件预编译,为了使代码方便与其他项目共享,避免显式包含此文件(precompile.cc除外),使用编译器选项/F1以自动包含;
4)通常名为resource.h,且只包含宏的资源头文件,不必拘泥于此风格指南。