Google+C+++编程规范

所需积分/C币:10 2015-02-26 16:56:43 733KB PDF
2
收藏 收藏
举报

C++ 是 Google 大部分开源项目的主要编程语言。正如每个 C++ 程序员都知道的,C++ 有很多强大的特性,但这种强大不可避免的导致它走向复杂,使代码更容易产生 bug,难以阅读和维护。   Google 经常会发布一些开源项目,意味着会接受来自其他代码贡献者的代码。但是如果代码贡献者的编程风格与 Google 的不一致,会给代码阅读者和其他代码提交这造成不小的困扰。Google 因此发布了这份自己的编程风格,使所有提交代码的人都能获知 Google 的编程风格。   创新工场董事长兼 CEO 李开复曾经对 Google C++ 编码规范给予了极高的评价:“我认为这是地球上最好的一
15.命名空间格式化( Namespace Formatting)…mmm48 16.水平留白( Horizontal whitespace)…, 17.垂直留白( Vertical Whitespace) 申有申申命电命电申电申事 九、规则之例外. 1.现有不统代码( Existing Non- conformant code)…-52 2. Windows代码( Windows code) 52 十、团队合作… 53 头文件 通常,每一个.CC文件(C++的源文件)都有一个对应的h文件(头文件),也有一些例 外,如单元测试代码和只包含main(的.CC文件。 正确使用头文件可令代码在可读性、文件大小和性能上大为改观。 下面的规则将引导你规避使用头文件时的各种麻烦。 1.# define的保护 所有头文件都应该使用# define防止头文件被多重包含( multiple inclusion),命名格式 当是:<PPO丿LCT><PAT<FIE>I 为保证唯一性,头文件的命名应基于其所在项目源代码树的全路径。例如,项目foo中的头 文件foo/src/bar/baz.h按如下方式保护: #ifndef foo bar baz h #define foo bar baz h fendi / Foo BAR Baz H 2。头文件依赖 使用前置声明( forward declarations)尽量减少.h文件中# include的数量。 当一个头文件被包含的同时也引入了一项新的依赖( dependency),只要该头文件被修改, 代码就要重新编译。如果你的头文件包含了其他头文件,这些头文件的任何改变也将导致那 些包含了你的头文件的代码重新编译。因此,我们宁可尽量少包含头文件,尤其是那些包含 在其他头文件中的 使用前置声明可以显著减少需要包含的头文件数量。举例说明:头文件中用到类File,但不 需要访问Fle的声明,则头文件中只需前置声明 class file;无需 include file/base/file.h"。 在头文件如何做到使用类FoO而无需访问类的定义? )将数据成员类型声明为Fo米或Foo& 2)参数、返回值类型为Foo的函数只是声明(但不定义实现); 3)静态数据成员的类型可以被声明为F0o,因为静态数据成员的定义在类定义之外 另·方面,如果你的类是Foo的了类,或者含有类型为Foo的非静态数据成员,则必须为 之包含头文件。 有时,使用指针成员( pointer members,如果是 scoped ptr更好)替代对象成员( object members)的确更有意义。然而,这样的做法会降低代码可读性及执行效率。如果仅仅为 了少包含头文件,还是不要这样替代的好。 当然,,CC文件无论如何都需要所使用类的定义部分,自然也就会包含若干头文件。 译者注:能依赖声明的就不要依赖定义。 3.内联函数 只有当函数只有10行甚至更少时才会将其定义为内联函数( inline function)。 定义( Definition):当函数被声明为内联函数之后,编译器可能会将其内联展开,无需 按通常的函数调用机制调用内联函数。 优点:当函数体比较小的时侯,内联该函数可以令目标代码更加高效。对于存取函数 ( accessor、 mutator)以及其他一些比较短的关键执行函数 缺点:滥用内联将导致程序变慢,內联有可能是目标代码量或増或减,这取决于被内联的函 数的大小。内联较短小的存取函数通常会减少代码量,但内联一个很大的函数(译者注:如 果编译器允许的话)将戏剧性的增加代码量。在现代处理器上,山于更好的利用指令缓存 ( instruction cache),小巧的代码往往执行更快。 结论:一个比较得当的处理规则是,不要内联超过10行的函数。对于析构函数应慎重对待, 析构函数往往比其表面看起来要长,因为有一些隐式成员和基类析构函数(如果有的话)被 周用 另一有用的处理规则:内联那些包含循环或 switch语句的函数是得不偿失的,除非在人多 数情况下,这些循环或 switch语句从不执行。 亘要的是,虚函数和递归函数即使被声明为内联的也不一定就是内联函数。通常,递归函数 不应该被声明为内联的(译者注:递归调用堆栈的展开并不像循环那么简单,比如递归层数 在编译时可能是未知的,大多薮编译器都不支持内联递归函数)。析构函数内联的主要原因 是其定义在类的定义中,为了方便抑或是对其行为给出文档 4.-inh文件 复杂的内联函数的定义,应放在后缀名为-inh的头文件中。 在头文件中给出内联函数的定义,可令编译器将其在调用处内联展开。然而,实现代码应完 仝放到.cc文件中,我们不希望.h文件中出现太多实现代码,除非这样做在可读性和效率上 有明显优势。 如果内联函数的定义比较短小、逻辑比较简单,其实现代码可以放在.h文件中。例如,存 取函数的实现理所当然都放在类定义中。出于实现和调用的方便,较复杂的内联函数也可以 放到h文件中,如果你觉得这样会使头文件显得笨重,还可以将其分离到单独的-n.h中。 这样即把实现和类定义分离开来,当需要吋包含实现所在的-inl.h即可。 -inl.h文件还可用于数模板的定义,从而使得模板定义可读性增强 要提醒的一点是,-inl.h和其他头文件一样,也需要# define保护。 5.函数参数顺序( Function Parameter Ordering) 定义函数时,参数顺序为:输入参数在前,输出参数在后 C/C++函数参数分为输入参数和输出参数两种,有时输入参数也会输岀(译者注:佰被修 改时)。输入参数一般传值或常数引用( const references),输出参数或输入/输出参数 为非常数指针(non- const pointers)。对参数排序时,将所有输入参数置」输出参数之 前。不要仅仅因为是新添加的参数,就将其置于最后,而应该依然置于输出参数之前。 这点并不是必须遵循的规则,输入/输岀两用参数(通常是类/结构体变量)混在其中,会 使得规则难以遵循。 5.包含文件的名称及次序 将包含次序标准化可增强可读性、避免隐藏依赖( hidden dependencies,译者注:隐藏 依赖主要是指包含的文件中编译时),次序如下:C库、C++库、其他库的.h、项目內的.h 项目内头文件应按照项目源代码目录树结构排列,并且避免使用UNX文件路径.(当前目 录)和,(父日录)。例如, google- awesome- project,/src/base/ logging. h应像这样 破包含 #include base/logging. h dir/fooC的主要作用是执行或测试dir2/foo2h的功能,foo.CC中包含头文件的次序如 dir2/foo2h(优先位胃,详情如下) C系统文件 C++系统文件 其他库头文件 本项目内头文件 这种排序方式可有效减少隐臧依赖,我们希望每一个头文件独立编译。最简单的实现方式是 将其作为第一个h文件包含在对应的CC中。 dir/foo.cC和dir2/foo2h通常位」相同目录下(像base/ basictypes unittest.Cc和 base/ basictypes h),但也可在不同目录下。 相同目录下头文件按字丹序是不错的选择。 举例来说, google- awesome-project/!src/foo/ internal/ fooserver,CC的包含次序如下 # include"foo/ public/ fooserver.h"//优先位置 #include <sys/ types.h> #include <unistd.h> #include <hash map> *include <vector> # include base/basicty pes h #include base/commandlineflags. h #include foo/public/bar. h 作用域 1。命名空间( Namespaces) 在,CC文件中,提倡使用不具名的命名空间( unnamed namespaces,译者注:不具名的 命名空间就像不具名的类一样,似乎被介绍的很少:-()。使用具名命名空间时,其名称可 基于项目或路径名称,不要使用 using指示符。 定义:命名空间将全局作用域细分为不同的、具名的作用域,可有效防止全局作用域的命名 中突 优点:命名空间提供了(可嵌套)命名轴线( name axis,详者注:将命名分割在不同命 名空间内),当然,类也提供了(可嵌套)的命名轴线(译者注:将命名分割在不同类的作 用域内 举例来说,两个不同项目的全局作用域都有一个类Fo,这样在编译或运行时造成冲突。如 果每个项目将代码置于不同命名空间中, project1::Foo和 project.2:Foo作为不同符号 自然不会冲突。 缺点:命名空间具有迷惑性,因为它们和类一样提供了额外的(可嵌套的)命名轴线。在头 文件中使用不具名的空间容易违背C++的唯一定义原则( One definition rule(ODR) 结论:根据下文将要提到的策略合理使用命名空间。 1)不具名命名空间( Unnamed Namespaces) 在.CC文件中,允许甚至提倡使用不具名命名空间,以避免运行时的命名冲突 namespace t //.cc文什中 //命名空间的内容无需缩进 enum i UNUSED, EOF, ERROR 3; /终常使用的符号 bool ateof(){ return pos==EOF;}//使用本命名空间内的符号EOF 3// namespace 然而,与特定类关联的文件作用域声明在该类中被声明为类型、静态数据成员或静态成员函 数,而不是不具名命名空间的成员。像上文展示的那样,不具名命名空问结束时用注释/ namespace标识。 不能在h文件中使用不具名命名空间。 2)具名命名空间( Named Namespaces) 具名命名空间使用方式如下: 命名空间将除文件包含、全局标识的声明/定义以及类的前置声明外的整个源文件封装起来, 以同其他命名空间相区分。 //.h文件 namespace mynamespace t /所有声明都置于命名空间中 ∥/注意不要使用缩进 class My class t publiC void Food; 3 / namespace mynamespace /.cc文件 namespace mynamespace i /函数定义都置于命名空间中 void MyClass: Foot s / namespace mynamespace 通常的CC文件会包含更多、更复杂的细节,包括对其他命名空间中类的引用等。 #include a h DEFINE_ bool(someflag, false, dummy flag") class o;//全局命名空间中类C的前置声明 namespace a{ class a;}//命名空间a中的类a:A的前置声明 namespace bt coge for ∥/b中的代码 1// namespace b 不要声明命名空间std下的任何内容,包括标准库类的前置声明。声明std下的实体会导 致不明桷的行为,如,不可移植。声明标准斥下的实体,需要包含对应的头文件。 最好不要使用 using指示符,以保证命名空间下的所有名称都可以正常使用。 /禁止一一污染命名空间 using namespace toO; 在CC文件、h文件的函数、方法或类中,可以使用 usIng。 //允许:C文件中 /.h文件中,必须在函数、方法或类的内部使用 using: foo: bar: 在.CC文件、h文件的函数、方法或类中,还可以使用命名空间别名。 /允许:.CC文件中 /.h文件中,必须在函数、方法或类的内部使用 namespace fbz=: foo: bar: baz; 2.嵌套类( Nested class) 当公丌嵌套类作为接∏的部分时,虽然可以直接将他们保持在全局作用域中,但将嵌套类 的声明置于命名空间中是更好的选择。 定义:可以在一个类中定义另一个类,嵌套类也称成员类( member class)。 class Foo t rivate /Bar是嵌套在Foo中的成员类 class Bar t 优点:当嵌套(成员)类只在被嵌套类( enclosing class)中使用吋很有用,将其置于被 嵌套类作用域作为被嵌套类的成员不会污染其他作用域同名类。可在被嵌套类中前置声明嵌 套类,在.CC文件中定义嵌套类,避免在被嵌套类中包含嵌套类的定义,因为嵌套类的定义 通常只与实现相关。 缺点:只能在被嵌套类的定义中才能前置声明嵌套类。因此,任何使用Foo:Bar*指针的 头文件必须包含整个Foo的声明。 结论:不要将嵌套类定义为 public,除非它们是接口的一部分,比如,某个方法使用了这 个类的一系列选项 3.非成员函数( Nonmember)、静态成员函数( Static member)和全局函数( clobal Functions) 使用命名空间中的非成员函数或静态成员函数,尽量不要使用全局函数。 优点:某些情况下,非成员函数和静态成员函数是非常有用的,将非成员函数置于命名空间 中可避免对全局作用域的污染。 缺点:将非成员函数和静态成员函数作为新类的成员或许更有意义,当它们需要访问外部资 源或具有重要依赖时更是如此。 结论: 有时,不把函数限定在类的实体中是有益的,甚至需要这么做,要么作为静态成员,要么作 为非成员函数。非成员函数不应依赖于外部变量,并尽量置于某个命名空间中。相比单纯为 了封装若干不共享任何静态数据的静态成员函数而创建类,不如使用命名空间 定义于同一编译单元的函数,被其他编译单元直接调用可能会引入不必要的耦合和迕接依 赖;静态成员函数对此尤其敏感。可以考虑提取到新类中,或者将函数置于独立库的命名空 间中。 如果你确实需要定义非成员函数,又只是在.CC文件中使用它,可使用不具名命名空间或 static关联(如 static int foo(..})限定其作用域。 4.局部变量( Local variab|es) 将函数变量尽可能置于最小作用域内,在声明变量时将其初始化 C++允许在函数的任何位置声明变量。我们提倡在尽可能小的作用域中声明变量,离第 次使用越近越好。这使得代码易于阅读,易于定位变量的声眀位置、变量类型和初始值。特 别是,应使用初始化代替声明+赋值的方式 int i i=f();//坏一一初始化和声明分离 ntj=g(;//好一一初始化时声明 注意:gcc可正确执行for(inti=0;i<10;++i)(i的作用域仅限for循环),因此其 他for循环中可重用i。诉和 While等语句中,作用域声明( scope declaration)同样是 正确的 while(const charx p= strchr(str,,))str=p+1 注意:如果变量是一个对象,每次进入作用域都要调用其构造函数,每次退出作用域都要调 用其析构函数。 /低效的实现 for(inti=0;i<1000000;++i){ Foof;/构造函数和析构函数分别调用1000000次 f DoSomething (i: 类似变量放到循环作用域外面声明要高效的多: Foof;∥/构造函数和析构函数只调用1次 for(inti=0;i<1000000;++){ f Dosomething(i) 5。全局变量( Global variables) class类型的仝局变量是被絷止的,内建关型的仝局变量是允许的,当然多线程代码中非常 数全局变量也是被禁止的。永远不要使用函数返回值初始化全局变量 不幸的是,全局变量的构造数、析构函数以及初始化操作的调用顺序只是被部分规定,每 次生成有可能会有变化,从而导致难以发现的bugs 因此,禁止使用 class类型的全局变量(包括STL的 string,Ⅴ ector等等),因为它们的 初始化顺序有可能导致构造出现问题。内建类型和由内建类型构成的没有构造睬数的结构体

...展开详情
试读 53P Google+C+++编程规范
立即下载 低至0.43元/次 身份认证VIP会员低至7折
一个资源只可评论一次,评论内容不能少于5个字
Dontpushme 很不错 大开眼界
2015-08-04
回复
您会向同学/朋友/同事推荐我们的CSDN下载吗?
谢谢参与!您的真实评价是我们改进的动力~
  • GitHub

    绑定GitHub第三方账户获取
  • 签到王者

    累计签到获取,不积跬步,无以至千里,继续坚持!
  • 分享宗师

    成功上传21个资源即可获取
关注 私信
上传资源赚积分or赚钱
最新推荐
Google+C+++编程规范 10积分/C币 立即下载
1/53
Google+C+++编程规范第1页
Google+C+++编程规范第2页
Google+C+++编程规范第3页
Google+C+++编程规范第4页
Google+C+++编程规范第5页
Google+C+++编程规范第6页
Google+C+++编程规范第7页
Google+C+++编程规范第8页
Google+C+++编程规范第9页
Google+C+++编程规范第10页
Google+C+++编程规范第11页

试读结束, 可继续读5页

10积分/C币 立即下载 >