I 关于编码规范
1.最重要的一点:明确规范,然后保持一致。BE CONSISTENT!
II Header Files
1.The #define Guard
Guard的命名规则为<PROJECT>_<PATH>_<FILE>_H_。比如foo工程中的文件foo/src/bar/baz.h应定义如下Guard:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
2.头文件依赖
在头文件中尽量使用forward declaration替代#include,这样可以减少头文件之间的相互依赖,从而可能使得增量编译时需要重新编译的编译单元减少。
如果要在一个头文件中使用类Foo,则在以下情况下,可以仅使用forward declaration:
1)声明类型为Foo *或Foo &的数据成员。
2)声明参数或返回类型为Foo的函数。
3)声明类型为Foo的静态数据成员。因为静态数据成员是在类定义之外定义的。
如果在源文件(.cpp或.cc)中需要使用符号Foo,应该直接在源文件中使用#include引入Foo的定义,而不是依赖于其它头文件的间接包含。一个例外是,如果Foo在myfile.cc中使用,可以在myfile.h中包含相应头文件。
3.内联函数
只有在函数足够小时才定义内联函数,比如小于10行。
不要把析构函数声明为内联函数,因为析构函数往往比你编写的要长得多,因为编译器会插入相关成员的析构操作。
不要将函数有loop和switch的函数声明的内联函数。
有些函数即使声明为内联函数也不会内联,比如虚函数和递归函数。
4.The -inl.h Files
由于内联函数需要在头文件中实现,这样编译器才能在相应函数被调用的地方插入内联函数实现代码。
对于简单的内联函数,比如accessors和mutators,应该在.h文件中定义。
更复杂的内联函数应该在单独的-inl.h文件中定义。
使用-inl.h文件的另一种情况是函数模板的定义,从而使得你的模板定义更具可读性。
5.函数参数的顺序
C++的函数参数有三种类型:input, output, or both. 规则是将输入参数放在输出参数之前。
通常输入参数应该是值类型或const引用,输出参数是非const指针。
6.Names and Order of Includes
头文件应分组包含,通常以如下顺序包含头文件:类定义头文件,C库,C++库,其它三方库,工程定义头文件。
所有工程定义的头文件,应该以工程源码根目录为起点,列出完整路径,绝不应该使用"."和".."来表示以当前目录为基准的相对路径。
例:
#include "foo/public/fooserver.h" // Preferred location.
#include <sys/types.h>
#include <unistd.h>
#include <hash_map>
#include <vector>
#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"
III Scoping
1.命名空间
命名空间定义结束时应使用注释标明对应的命名空间。
1.1 Unnamed namespaces
鼓励在.cc文件中使用Unnamed namespaces,这样可以防止运行时命名冲突。通常将局部于.cc文件的一些helper函数放在Unnamed namespace中。
绝不在.h文件中使用Unnamed namespace.
namespace { // This is in a .cc file.
// The content of a namespace is not indented
enum { kUnused, kEOF, kError }; // Commonly used tokens.
bool AtEof() { return pos_ == kEOF; } // Uses our namespace's EOF.
} // namespace
1.2 Named namespaces
在头文件和forward declarations之后,命名空间包含整个源文件。
绝不使用using指令导入整个命名空间,以免造成命名空间污染。可以使用using导入单个符号。
using指令可以出现在.cc文件中的任何位置,以及头文件的函数、方法和类中。
1.3 Namespace alias
命名空间别名可以出现在.cc文件中的任何位置,头文件中包含整个头文件的命名空间内,以及头文件中的函数和方法中。
如果在.h中使用了命名空间别名,则包含此头文件的文件也可以见到此别名,因此在头文件中使用命名空间别名应慎重。
// Shorten access to some commonly used names in .cc files.
namespace fbz = ::foo::bar::baz;
// Shorten access to some commonly used names (in a .h file).
namespace librarian {
// The following alias is available to all files including
// this header (in namespace librarian):
// alias names should therefore be chosen consistently
// within a project.
namespace pd_s = ::pipeline_diagnostics::sidetable;
inline void my_inline_function() {
// namespace alias local to a function (or method).
namespace fbz = ::foo::bar::baz;
...
}
} // namespace librarian
2.嵌套类
不要使用public nested class,除非你确定它是接口的一部分。这种情况下直接定义这个类(不定义为嵌套类)通常更直观。更进一步,可以使用命令空间来达到将这个类和全局作用域隔离的目的。
3.Nonmember, Static Member, and Global Functions
有一些函数不属于任何类或者不适合作为某个类的成员,则最好的处理方式是将其定义为非成员函数,并声明一个命名空间来管理这些函数。不要仅为了组织一组函数而定义一个类。
如果这些函数只在当前.cc文件中使用,则可以使用unnamed namespace或者声明为static linkage。
如果这些函数共享某些静态数据,则应该将它们定义为一个新的类的静态成员函数。
尽量不使用全局函数。
4.局部变量
让局部变量的作用域尽可能小,并且在定义的时候初始化,而不是先定义再赋值。
应该在使用变量之前才定义变量,不使用以前C风格的在函数一开始定义所有局部变量的方式。
5.静态变量和全局变量
绝不使用类类型的静态变量或全局变量,因为对象创建和销毁顺序的不确定性往往造成难以发现的bug。
具有静态存储 (static storage duration)的对象,包括静态变量、全局变量、类的静态成员变量和函数内的静态变量,都应该是“平凡数据对象 (POD, Plain Old Data)”:ints, chars, floats, 指针或者以 POD为元素的arrays/structs 。
C++规范没有完备地说明静态成员初始化式的调用顺序,因此不要用函数调用来初始化静态POD变量,除非调用的函数不依赖于其它全局的东西。
对象的销毁顺序与创建顺序相反,因为创建顺序不确定,所以销毁的顺序也是不确定的(例如,在程序结束时,一个静态变量可能已经被销毁了,但是代码依然在执行(可能在另一个线程中)并且试图访问这个静态变量)。结果是静态变量只允许为POD,这一规则完全排除了将vector(使用C风格数组)和string(使用const char[])作为静态变量使用。
如果一定要将一个类对象作为静态或全局的,应该使用指针(不会被释放),然后在main函数初始化(或者使用pthread_once()初始化)。要注意这个指针应该是“平凡”的指针(raw pointer),而不是智能指针(smart pointer)。