google开源 C / C ++项目代码规范

每通常一个  .cc 文件都有一个对应的  .h 文件。也有一些常见例外,如单元测试代码和只包含  main() 函数的  .cc 文件。

正确使用头文件可令代码在可读性,文件大小和性能上大为改观。

下面的规则将引导你规避使用头文件时的各种陷阱。

1.1  自包含的头文件

头文件应该能够自给自足(自包含的,也就是可以作为第一个头文件被引入),以  .h 结尾。至于用来插入文本的文件,说到底它们并不是头文件,所以以应  .inc 结尾不允许。出分离  -inl.h 头文件的做法。

所有头文件要能够自给自足。换言之,用户和重构工具不需要为特别场合而包含额外的头文件。详言之,一个头文件要有  1.2。#define保护,统计包含它所需要的其它头文件,也不要求定义任何特别的符号。

不过有一个例外,即一个文件并不是自足的,而是作为文本插入到代码某处。或者,文件内容实际上是其它头文件的特定平台(特定于平台)扩展部分,这些文件就要用  .inc 文件扩展名。

如果  .h 文件声明了一个模板或内联函数,同时也在该文件加以定义。有用凡是到这些的  .cc 文件,就得统统包含该头文件,否则程序可能会在构建中链接失败。不要把这些定义放到分离的  -inl.h 文件里(译者注:过去该规范曾提倡把定义放到-inl.h里过)。

有个例外:如果某函数模板为所有相关模板参数显式实例化,或本身就是某类的一个私有成员,它就那么定义只能在实例化该模板的  .cc 文件里。

1.2  #define保护

所有头文件都应该使用  #define 来防止头文件被多重包含,命名格式当是:  <PROJECT>_<PATH>_<FILE>_H_ 。

为保证唯一性,头文件的命名应该基于所有项目源代码树的全路径。例如,项目  foo 中的头文件  foo/src/bar/baz.h 可按如下方式保护:#ifndef FOO_BAR_BAZ_H_ 

#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

1.3  前置声明

尽可能地避免使用前置声明。使用  #include 所有游戏需要的头文件即可。

定义:

所谓「前置声明」(forward declaration)是类,函数和模板的纯粹声明,没伴随着其定义。
优点:

前置声明能够节省编译时间,的多余  #include 会迫使compile-器展开更多的文件,处理更多的输入。
前置声明能够节省不必要的重新编译的时间。  #include 使代码因为头文件中无关的改动而被重新编译多次。

缺点:

前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。

前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其API。例如扩大形参类型,加个自带默认参数的模板形参等等。

前置声明来自命名空间  std:: 的符号时,其行为未定义。

很难判断什么时候该用前置声明,时候什么用该  #include 极端情况下,用前置声明代替。  includes 甚至都会暗暗地改变代码的含义:

// bh:
struct B {};
struct D : B {}

// good_user.cc:
#包括 “BH”
void f (B * );
void f (void * );
void test (D * x ) { f (x ); } //调用f(B *)
如果  #include 被  B 状语从句:  D 的前置声明替代,  test() 就会调用  f(void*) 。
前置声明了include 不少来自头文件的符号时,就会比单单一行的  冗长。
仅仅为了能前置声明而重构代码(比如用指针成员代替对象成员)会使代码变得更慢更复杂。
结论:

尽量避免前置声明那些定义在其他项目中的实体。
函数:总是使用  #include。
类模板:优先使用  #include。

1.4 内联函数

只有当函数只有10行甚至更少时才将其定义为内联函数。

定义:

当函数被声明为内联函数之后,编译器会将其内联展开,而不是按通常的函数调用机制进行调用。
优点:

只要内联的函数体小小,内联该函数可以令目标代码更加高效。对于存取函数以及其它函数体比较短,性​​能关键的函数,鼓励使用内联。
缺点:

滥用内联将导致程序变得更慢。内联可能使目标代码量或增或减,这取决于内联函数的大小。内联非常短小的存取函数通常会减少代码大小,但内联一个相当大的函数将戏剧性的增加代码大小。现代处理器由于更好的利用了指令缓存,小巧的代码往往执行更快。
结论:

一个较为合理的经验准则是,不要内联超过10行的函数。谨谨对待析构函数,析构函数往往比其表面看起来要更长,因为有隐含的成员和基类析构函数被调用!

另一个实用的经验准则:内联那些包含循环或  switch 语句的函数常常是得不偿失(除非在大多数情况下,这些循环或  switch 语句从不被执行)。

有些函数即使声明为内联的也不一定会被编译器内联,这点很重要; 比如虚函数和递归函数就不会被正常内联。通常,递归函数不应该声明成内联函数。(YuleFox注:递归调用堆栈的展开并不像循环那么简单,比如递进层数在编译时可能是未知的,大多数编译器都不支持内联递归函数)。虚函数内联的主要原因是想把它的函数体放在类定义内,为了图个方便,抑或是当作文件描述其行为,比如精短的存取函数。

1.5  #include 的路径及顺序

使用标准的头文件包含顺序可增强可读性,避免隐藏依赖:相关头文件,C库,C ++库,其他库的  .h,本项目内的  .h。

项目内部文件应按照项目源代码目录树结构排列,避免使用UNIX特殊的快捷目录  .(当前目录)或  .. (上级目录)。例如,  google-awesome-project/src/base/logging.h 应该按如下方式包含:

#include “base / logging.h”
又如,  dir/foo.cc 或  dir/foo_test.cc 的主要作用英文的英文实现或测试  dir2/foo2.h 的功能,  foo.cc 中包含头文件的次序如下:

dir2/foo2.h (优先位置,详情如下)
C系统文件
C ++系统文件
库其他的  .h 文件
项目本。内  .h 文件
优先这种顺序的排序保证当  dir2/foo2.h 遗漏某些必要的库时,  dir/foo.cc 或  dir/foo_test.cc 的构建会立刻中止。因此这一条规则保证维护这些文件的人们首先看到构建中止的消息而不是维护其他包的人们。

dir/foo.cc 和  dir2/foo2.h 通常位于同一目录下(如  base/basictypes_unittest.cc 和  base/basictypes.h),但也可放在不同目录下。

按字母顺序分别对每种类型的头文件进行二次排序是不错的主意。注意较老的代码可不符合这条规则,要在方便的时候改正它们。

您所依赖的符号(符号)被哪些头文件所定义,您就应该包含(包括)哪些头文件,前置声明  (向前声明)情况除外。您比如要用到  bar.h 中的某个符号,哪怕您所包含的  foo.h 已经包含了  bar.h,也照样得包含  bar.h,除非foo.h 有明确  说明它会自动向您提供  bar.h 中符号。不过,凡是cc文件所对应的「相关头文件」已经包含的,就不用再重复包含进其cc文件里面了,就像  foo.cc 只包含  foo.h就够了,不用再管后者所包含的其它内容。

 

2.函数

2.1 参数顺序

总述

函数的参数顺序为:输入参数在先,后跟输出参数。

说明

C / C ++中的函数参数或者是函数的输入,或者是函数的输出,或兼而有之。输入参数通常是值参或  const 引用,输出参数或输入/输出参数则一般为非  const 指针。在排列参数顺序时,将所有的输入参数置于输出参数之前。特别要注意,在加入新参数时不要因为它们是新参数就置于参数列表最后,而是仍然要按照前述的规则,即将新的输入参数也置于输出参数之前。

这并非一个硬性规定。输入/输出参数(通常是类或结构体)让这个问题变得复杂。并且,有时候为了其他函数保持一致,你可能不得不不所有变通。

2.2 编写简短函数

总述

我们倾向于编写简短,凝练的函数。

说明

我们承认长函数有时是合理的,因此并不硬限制函数的长度。如果函数超过40行,可以思索一下能不能在不影响程序结构的前提下对其进行分割。

即使一个长函数现在工作的非常好,一旦有人对其修改,有可能出现新的问题,甚至导致难以发现的错误。使函数尽量简短,以便于他在他人阅读和修改代码。

在处理代码时,你可能会发现复杂的长函数。不要害怕修改现有代码:如果证实这些代码使用/调试起来很困难,或者你只需​​要使用其中的一小段代码,考虑将其分割为更加简短并易于管理的若干函数。

2.3 引用参数

总述

所有按引用传递的参数必须加上  const。

定义

在C语言中,如果函数需要修改变量的值,参数必须为指针,如  。在C ++中,函数还可以声明为引用参数:  。int foo(int *pval)int foo(int &val)

优点

引用定义参数可以防止出现  (*pval)++ 这样丑陋的代码。引用参数对于拷贝构造函数这样的应用也是必需的。同时也更明确地不接受空指针。

缺点

容易引起误解,因为引用在语法上是值变量却拥有指针的语义。

结论

函数参数列表中,所有引用参数都必须是  const:

void Foo (const string &in , string * out );
事实上这在Google Code是一个硬性约定:输入参数是值参或  const 引用,输出参数为指针。输入参数可以是  const 指针,但决不能是非  const 引用参数,除非特殊要求,比如  swap()。

有时候,在输入形参中用针指   比   更明智。比如:const T*const T&

可能会传递空指针。
函数要把指针或对地址的引用赋值给输入形参。
总而言之,大多时候输入形参往往是  。用若   则说明输入侧另有处理。所以若要使用  ,则应给出相应的理由,否则会使读者感到迷惑。const T&const T*const T*

2.4  函数重载

总述

若要使用函数重载,则必须能让读者一看调用点就胸有成竹,而不用花心思猜测调用的重载函数到底是哪一种。这一规则也适用于构造函数。

定义

你可以编写一个参数类型为   的函数,然后用另一个参数类型为   的函数对其进行重载:const string&const char*

class MyClass {
public :
void Analyze (const string &text );
void 分析(const char * text , size_t textlen );
};
优点

通过重载参数不同的同名函数,可以令代码更直观。模板化代码需要重载,这同时也能为使用者带来便利。

缺点

如果函数单靠不同的参数类型而重载(acgtyrant注:这意味着参数数量不变),读者就得十分熟悉C ++五花八门的匹配规则,以了解匹配过程具体到底如何。另外,如果派生类只重载了某个函数的部分变体,继承语义就容易令人困惑。

结论

如果打算重载一个函数,可以试试改在函数名里加参数信息。例如,用  AppendString()和  AppendInt() 等,而不是一口气重载多个  Append()。如果重载函数的目的是为了支持不同数量的同一类型参数,则优先考虑使用  std::vector 以便使用者可以用  列表初始化指定参数。

2.5 缺省参数

总述

只允许在非虚函数中使用缺省参数,且必须保证缺省参数的值始终一致。参数缺省与  函数重载  遵循同样的规则。一般情况下建议使用函数重载,尤其是在缺省函数带来的可读性提升不能弥补下文中所提到的缺点的情况下。

优点

有些函数一般情况下使用默认参数,但有时需要又使用非默认的参数。缺省参数为这样的情形提供了便利,使程序员不需要为了极少的例外情况编写大量的函数。和函数重载相比,缺省参数的语法更简洁明了,减少了大量的样板代码,也更好地区别了“必要参数”和“可选参数”。

缺点

缺省参数实际上是函数重载语义的另一种实现方式,因此所有  不应当使用函数重载的理由  也都适用于缺省参数。

虚函数调用的缺省参数取决于目标对象的静态类型,此时无法保证给定函数的所有重载声明的都是同样的缺省参数。

缺省参数是在每个调用点都要进行重新求值的,这会造成生成的代码迅速膨胀。作为读者,一般来说也更希望缺省的参数在声明时就已经被固定了,而不是在每次调用时都可能会有不同的取值。

缺省参数会干扰函数指针,导致函数签名与调用点的签名不一致。而函数重载不会导致这样的问题。

结论

对于虚函数,不允许使用缺省参数,因为在虚函数中缺省参数不一定能正常工作。如果在每个调用点缺省参数的值都有可能不同,在这种情况下缺省函数也不允许使用。(例如,不要写像   这样的代码。)void f(int n = counter++);

在其他情况下,如果缺省参数对可读性的提升远远超过了以上提及的缺点的话,可以使用缺省参数。如果仍有疑惑,就使用函数重载。

2.6  函数返回类型后置语法

总述

只有在常规写法(返回类型前置)不便于书写或不便于阅读时使用返回类型后置语法。

定义

C ++现在允许两种不同的函数声明方式。以往的写法是将返回类型置于函数名之前。例如:

int foo (int x );
C ++ 11引入了这一新的形式。现在可以在函数名前使用  auto 关键字,在参数列表之后后置返回类型。例如:

auto foo (int x ) - > int ;
后置返回类型为函数作用域。对于像  int 这样简单的类型,两种写法没有区别。但对于复杂的情况,例如类域中的类型声明或者以函数参数的形式书写的类型,写法的不同会造成区别。

优点

后置返回类型是显式地指定  Lambda表达式  的返回值的唯一方式。某些情况下,编译器可以自动推导出Lambda表达式的返回类型,但并不是在所有的情况下都能实现。即使编译器能够自动推导,显式地指定返回类型也能让读者更明了。

有时在已经出现了的函数参数列表之后指定返回类型,能够让书写更简单,也更易读,尤其是在返回类型依赖于模板参数时。例如:

template < class T , class U > auto add (T t , U u ) - > decltype (t + u );
对比下面的例子:

template < class T , class U > decltype (declval < T &> () + declval < U &gt ;) add (T t , U u );
缺点

后置返回类型相对来说是非常新的语法,而且在C和Java中都没有相似的写法,因此可能对读者来说比较陌生。

在已有的代码中有大量的函数声明,你不可能把它们都用新的语法重写一遍。因此实际的做法只能是使用旧的语法或者新旧混用。在这种情况下,只使用一种版本是相对来说更规整的形式。

结论

在大部分情况下,应当继续使用以往的函数声明写法,即将返回类型置于函数名前。只有在必要的时候(如Lambda表达式)或者使用后置语法能够简化书写并且提高易读性的时候才使用新的返回类型后置语法。但是后一种情况一般来说是很少见的,大部分时候都出现在相当复杂的模板代码中,而多数情况下不鼓励写这样  复杂的模板代码。

3. 通用命名规则


总述

函数命名,变量命名,文件命名要有描述性; 少用缩写。

说明

尽可能使用描述性的命名,别心疼空间,毕竟相比之下让代码易于新读者理解更重要。不要用只有项目开发者能理解的缩写,也不要通过砍掉几个字母来缩写单词。

int price_count_reader ; //无缩写
int num_errors ; //“num”是一个常见的写法
int num_dns_connections ; //人人都知道“DNS”是什么
int n ; //毫无意义。
int nerr ; //含糊不清的缩写。
int n_comp_conns ; //含糊不清的缩写。
int wgc_connections ; //只有贵团队知道是什么意思
int pc_reader ; //“pc”有太多可能的解释了。
int cstmr_id ; //删减了若干字母。
注意,一些特定的广为人知的缩写是允许的,例如用 i 表示迭代变量和用 T 表示模板参数。

模板参数的命名应当遵循对应的分类:类型模板参数应当遵循  类型命名  的规则,而非类型模板应当遵循变量命名的规则。

4. 文件命名

总述

文件名要全部小写,可以包含下划线(_)或连-字符(),依照项目的约定。如果没有约定,那么“ _” 更好。

说明

可接受的文件命名示例:

my_useful_class.cc
my-useful-class.cc
myusefulclass.cc
myusefulclass_test.cc //  _unittest 状语从句:  _regtest 已弃用。
C ++文件要以  .cc 结尾,头文件以  .h 结尾。专门插入文本的文件则以  .inc 结尾,参见  头文件自足。

不要使用已经存在于  /usr/include 下的文件名(Yang.Y注:即编译器搜索系统头文件的路径),如  db.h。

通常应尽量让文件名更加明确。  http_server_logs.h 就比  logs.h 要好。定义类时文件名一般成对出现,如  foo_bar.h 和  foo_bar.cc,对应于类  FooBar。

联内必须函数放在  .h 文件中。如果内联函数比较短,就直接放在  .h 中。

01-14 21:27
查看更多