Google开源项目风格指南有些函数即使声明为内联的也不一定会被编译器内联,这点很重要;比如虚函数和递归函数就不会被正常内联.通常,递归函数不应该声明成内联函数(YuleFox注:递归调用堆栈的展开并不像循环那么简单,比如递归层数在编译时可能是未知的,大多数编译器都不支持内联递归函数),虚函数内联的主要原因则是想把它的函数体放在类定义内,为了图个方便,抑或是当作文档描述其行为,比如精短的存取函数2.2.51.5.#include的路径及顺序Tip:使用标准的头文件包含顺序可增强可读性,避免隐藏依赖:相关头文件,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中包含头文件的次序如下:1.dir2/foo2.h(优先位置,详情如下)2.C系统文件3.C++系统文件4.其他库的.h文件5.本项目内.h文件这种优先的顺序排序保证当dir2/foo2.h遗漏某些必要的库时,dir/foo.cc或dir/footest.cc的构建会立刻中止。因此这一条规则保证维护这些文件的人们首先看到构建中止的消息而不是维护其他包的人们。dir/foo.cc和dir2/foo2.h通常位于同一目录下(如base/basictypes_unittest.cc和base/basictypes.h),但也可以放在不同目录下.按字母顺序分别对每种类型的头文件进行二次排序是不错的主意。注意较老的代码可不符合这条规则,要在方便的时候改正它们。您所依赖的符号(symbols)被哪些头文件所定义,您就应该包含(include)哪些头文件,前置声明(forwarddeclarations)情况除外。比如您要用到bar.h中的某个符号,哪怕您所包含的foo.h已经包含了bar.h,也照样得包含bar.h,除非foo.h有明确说明它会自动向您提供bar.h中的symbol.不过,凡是cc文件所对应的「相关头文件」已经包含的,就不用再重复包含进其cc文件单面了,就像foo.cc只包含foo.h就够了,不用再管后者所包含的其它内容。举例来说,google-awesome-project/src/foo/internal/fooserver.cc的包含次序如下:#include"foo/public/fooserver.h"//优先位置(continues on nextpage)8Chapter2.C++风格指南-内容目录
Google 开源项目风格指南 有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就 不会被正常内联. 通常, 递归函数不应该声明成内联函数.(YuleFox 注: 递归调用堆栈的展开 并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归 函数). 虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作 文档描述其行为, 比如精短的存取函数. 2.2.5 1.5. #include 的路径及顺序 Tip: 使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖: 相关头文件, 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 中包含 头文件的次序如下: 1. dir2/foo2.h (优先位置, 详情如下) 2. C 系统文件 3. C++ 系统文件 4. 其他库的 .h 文件 5. 本项目内 .h 文件 这种优先的顺序排序保证当 dir2/foo2.h 遗漏某些必要的库时,dir/foo.cc 或 dir/foo_test.cc 的构 建会立刻中止。因此这一条规则保证维护这些文件的人们首先看到构建中止的消息而不是维护其他包的 人们。 dir/foo.cc 和 dir2/foo2.h 通常位于同一目录下 (如 base/basictypes_unittest.cc 和 base/ basictypes.h), 但也可以放在不同目录下. 按字母顺序分别对每种类型的头文件进行二次排序是不错的主意。注意较老的代码可不符合这条规则,要 在方便的时候改正它们。 您所依赖的符号 (symbols) 被哪些头文件所定义,您就应该包含(include)哪些头文件,前置声明 (forward declarations) 情况除外。比如您要用到 bar.h 中的某个符号, 哪怕您所包含的 foo.h 已经包含了 bar.h, 也照样得包含 bar.h, 除非 foo.h 有明确说明它会自动向您提供 bar.h 中的 symbol. 不过,凡是 cc 文件 所对应的「相关头文件」已经包含的,就不用再重复包含进其 cc 文件里面了,就像 foo.cc 只包含 foo.h 就够了,不用再管后者所包含的其它内容。 举例来说, google-awesome-project/src/foo/internal/fooserver.cc 的包含次序如下: #include "foo/public/fooserver.h" // 优先位置 (continues on next page) 8 Chapter 2. C++ 风格指南 - 内容目录
Google开源项目风格指南(continued from previous page)#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"例外:有时,平台特定(system-specific)代码需要条件编译(conditionalincludes),这些代码可以放到其它includes之后。当然,您的平台特定代码也要够简练且独立,比如:#include"foo/public/fooserver.h"#include "base/port.h" // For LANG_CxXi1#ifdef LANG_CXX11#include<initializer_list>#endif//LANG_CXX112.2.6译者(YuleFox)笔记1.避免多重包含是学编程时最基本的要求;2.前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;3.内联函数的合理使用可提高代码执行效率;4.-inl.h可提高代码可读性(一般用不到吧:D):5.标准化函数参数顺序可以提高可读性和易维护性(对函数参数的堆栈空间有轻微影响,我以前大多是相同类型放在一起);6.包含文件的名称使用。和.·虽然方便却易混乱,使用比较完整的项目路径看上去很清晰,很条理包含文件的次序除了美观之外,最重要的是可以减少隐藏依赖,使每个头文件在“最需要编译”(对应源文件处:D)的地方编译,有人提出库文件放在最后,这样出错先是项目内的文件,头文件都放在对应源文件的最前面,这一点足以保证内部错误的及时发现了2.2.7译者(acgtyrant)笔记1.原来还真有项目用#includes来插人文本,且其文件扩展名.inc看上去也很科学。2.Google已经不再提倡-inl.h用法。92.2.1.头文件
Google 开源项目风格指南 (continued from previous page) #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" 例外: 有时,平台特定(system-specific)代码需要条件编译(conditional includes),这些代码可以放到其它 includes 之后。当然,您的平台特定代码也要够简练且独立,比如: #include "foo/public/fooserver.h" #include "base/port.h" // For LANG_CXX11. #ifdef LANG_CXX11 #include <initializer_list> #endif // LANG_CXX11 2.2.6 译者 (YuleFox) 笔记 1. 避免多重包含是学编程时最基本的要求; 2. 前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应; 3. 内联函数的合理使用可提高代码执行效率; 4. -inl.h 可提高代码可读性 (一般用不到吧:D); 5. 标准化函数参数顺序可以提高可读性和易维护性 (对函数参数的堆栈空间有轻微影响, 我以前大多 是相同类型放在一起); 6. 包含文件的名称使用 . 和 . 虽然方便却易混乱, 使用比较完整的项目路径看上去很清晰, 很条理, 包含文件的次序除了美观之外, 最重要的是可以减少隐藏依赖, 使每个头文件在“最需要编译”(对 应源文件处:D) 的地方编译, 有人提出库文件放在最后, 这样出错先是项目内的文件, 头文件都放在 对应源文件的最前面, 这一点足以保证内部错误的及时发现了. 2.2.7 译者(acgtyrant)笔记 1. 原来还真有项目用 #includes 来插入文本,且其文件扩展名 .inc 看上去也很科学。 2. Google 已经不再提倡 -inl.h 用法。 2.2. 1. 头文件 9
Google开源项目风格指南3.注意,前置声明的类是不完全类型(incompletetype),我们只能定义指向该类型的指针或引用,或者声明(但不能定义)以不完全类型作为参数或者返回类型的函数。毕竞编译器不知道不完全类型的定义,我们不能创建其类的任何对象,也不能声明成类内部的数据成员。4.类内部的函数一般会自动内联。所以某函数一旦不需要内联,其定义就不要再放在头文件里,而是放到对应的,cc文件里。这样可以保持头文件的类相当精炼,也很好地贯彻了声明与定义分离的原则。5.在#include中插入空行以分割相关头文件,C库,C++库,其他库的.h和本项目内的.h是个好习惯。2.32.作用域2.3.12.1.命名空间Tip:鼓励在.cc文件内使用匿名命名空间或static声明.使用具名的命名空间时,其名称可基于项目名或相对路径.禁止使用using指示(using-directive)。禁止使用内联命名空间(inlinenamespace)。定义:命名空间将全局作用域细分为独立的,具名的作用域,可有效防止全局作用域的命名冲突优点:虽然类已经提供了(可嵌套的)命名轴线(YuleFox注:将命名分割在不同类的作用域内),命名空间在这基础上又封装了一层,举例来说,两个不同项目的全局作用域都有一个类Foo,这样在编译或运行时造成冲突.如果每个项目将代码置于不同命名空间中,project1::Foo和project2::Foo作为不同符号自然不会冲突,内联命名空间会自动把内部的标识符放到外层作用域,比如namespace X(inline namespaceYvoid foo();}//namespaceY}// namespaceXX::Y::foo()与X::foo()彼此可代替。内联命名空间主要用来保持跨版本的ABI兼容性。缺点:命名空间具有迷惑性,因为它们使得区分两个相同命名所指代的定义更加困难。内联命名空间很容易令人迷惑,毕竟其内部的成员不再受其声明所在命名空间的限制。内联命名空间只在大型版本控制里有用。有时候不得不多次引用某个定义在许多嵌套命名空间里的实体,使用完整的命名空间会导致代码的允长。10Chapter2.C++风格指南-内容目录
Google 开源项目风格指南 3. 注意,前置声明的类是不完全类型(incomplete type),我们只能定义指向该类型的指针或引用,或 者声明(但不能定义)以不完全类型作为参数或者返回类型的函数。毕竟编译器不知道不完全类型 的定义,我们不能创建其类的任何对象,也不能声明成类内部的数据成员。 4. 类内部的函数一般会自动内联。所以某函数一旦不需要内联,其定义就不要再放在头文件里,而是 放到对应的 .cc 文件里。这样可以保持头文件的类相当精炼,也很好地贯彻了声明与定义分离的原 则。 5. 在 #include 中插入空行以分割相关头文件, C 库, C++ 库, 其他库的 .h 和本项目内的 .h 是个好 习惯。 2.3 2. 作用域 2.3.1 2.1. 命名空间 Tip: 鼓励在 .cc 文件内使用匿名命名空间或 static 声明. 使用具名的命名空间时, 其名称可基于项目 名或相对路径. 禁止使用 using 指示(using-directive)。禁止使用内联命名空间(inline namespace)。 定义: 命名空间将全局作用域细分为独立的, 具名的作用域, 可有效防止全局作用域的命名冲突. 优点: 虽然类已经提供了(可嵌套的)命名轴线 (YuleFox 注: 将命名分割在不同类的作用域内), 命 名空间在这基础上又封装了一层. 举例来说, 两个不同项目的全局作用域都有一个类 Foo, 这样在编译或运行时造成冲突. 如果 每个项目将代码置于不同命名空间中, project1::Foo 和 project2::Foo 作为不同符号自然 不会冲突. 内联命名空间会自动把内部的标识符放到外层作用域,比如: namespace X { inline namespace Y { void foo(); } // namespace Y } // namespace X X::Y::foo() 与 X::foo() 彼此可代替。内联命名空间主要用来保持跨版本的 ABI 兼容性。 缺点: 命名空间具有迷惑性, 因为它们使得区分两个相同命名所指代的定义更加困难。 内联命名空间很容易令人迷惑,毕竟其内部的成员不再受其声明所在命名空间的限制。内联 命名空间只在大型版本控制里有用。 有时候不得不多次引用某个定义在许多嵌套命名空间里的实体,使用完整的命名空间会导致 代码的冗长。 10 Chapter 2. C++ 风格指南 - 内容目录
Google开源项目风格指南在头文件中使用匿名空间导致违背C++的唯一定义原则(OneDefinitionRule(ODR)),结论:根据下文将要提到的策略合理使用命名空间·遵守命名空间命名中的规则。·像之前的几个例子中一样,在命名空间的最后注释出命名空间的名字。用命名空间把文件包含,gfags的声明/定义,以及类的前置声明以外的整个源文件封装起来,以区别于其它命名空间://.h文件namespace mynamespace{//所有声明都置于命名空间中//注意不要使用缩进class Myclass {public:...void Foo();1;// namespace mynamespace.cc文件1/namespace mynamespace {//函数定义都置于命名空间中void MyClass::Foo()...1// namespace mynamespace更复杂的.cc文件包含更多,更复杂的细节,比如gflags或using声明。#include "a.h"DEFINE_FLAG(bool,someflag,false,"dummyflag");namespace a (// 左对齐...code for a...// namespaceaY112.3.2.作用域
Google 开源项目风格指南 在头文件中使用匿名空间导致违背 C++ 的唯一定义原则 (One Definition Rule (ODR)). 结论: 根据下文将要提到的策略合理使用命名空间. • 遵守 命名空间命名 中的规则。 • 像之前的几个例子中一样,在命名空间的最后注释出命名空间的名字。 • 用命名空间把文件包含, gflags 的声明/定义, 以及类的前置声明以外的整个源文件封装 起来, 以区别于其它命名空间: // .h 文件 namespace mynamespace { // 所有声明都置于命名空间中 // 注意不要使用缩进 class MyClass { public: . void Foo(); }; } // namespace mynamespace // .cc 文件 namespace mynamespace { // 函数定义都置于命名空间中 void MyClass::Foo() { . } } // namespace mynamespace 更复杂的 .cc 文件包含更多, 更复杂的细节, 比如 gflags 或 using 声明。 #include "a.h" DEFINE_FLAG(bool, someflag, false, "dummy flag"); namespace a { .code for a. // 左对齐 } // namespace a 2.3. 2. 作用域 11
Google开源项目风格指南不要在命名空间std内声明任何东西,包括标准库的类前置声明,在std命名空间声明实体是未定义的行为,会导致如不可移植,声明标准库下的实体,需要包含对应的头文件·不应该使用using指示引人整个命名空间的标识符号。//禁止—污染命名空间using namespace foo;。不要在头文件中使用命名空间别名除非显式标记内部命名空间使用。因为任何在头文件中引人的命名空间都会成为公开API的一部分。//在.cc中使用别名缩短常用的命名空间namespace baz = ::foo::bar::baz;//在.h中使用别名缩短常用的命名空间namespacelibrarian(namespaceimpl(//仅限内部使用namespacesidetable=::pipeline_diagnostics::sidetable;-//namespaceimplinline void my_inline function()//限制在一个函数中的命名空间别名namespace baz =::foo::bar::baz;//namespacelibrariant·禁止用内联命名空间2.3.22.2.匿名命名空间和静态变量Tip:在.cc文件中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为static。但是不要在.h文件中这么做。定义:所有置于匿名命名空间的声明都具有内部链接性,函数和变量可以经由声明为static拥有内部链接性,这意味着你在这个文件中声明的这些标识符都不能在另一个文件中被访问。即使两个文件声明了完全一样名字的标识符,它们所指向的实体实际上是完全不同的结论:推荐、鼓励在.cc中对于不需要在其他地方引用的标识符使用内部链接性声明,但是不要在.h中使用。匿名命名空间的声明和具名的格式相同,在最后注释上namespace:12Chapter2.C++风格指南-内容目录
Google 开源项目风格指南 • 不要在命名空间 std 内声明任何东西, 包括标准库的类前置声明. 在 std 命名空间声明 实体是未定义的行为, 会导致如不可移植. 声明标准库下的实体, 需要包含对应的头文件. • 不应该使用 using 指示引入整个命名空间的标识符号。 // 禁止 —— 污染命名空间 using namespace foo; • 不要在头文件中使用 命名空间别名除非显式标记内部命名空间使用。因为任何在头文件 中引入的命名空间都会成为公开 API 的一部分。 // 在 .cc 中使用别名缩短常用的命名空间 namespace baz = ::foo::bar::baz; // 在 .h 中使用别名缩短常用的命名空间 namespace librarian { namespace impl { // 仅限内部使用 namespace sidetable = ::pipeline_diagnostics::sidetable; } // namespace impl inline void my_inline_function() { // 限制在一个函数中的命名空间别名 namespace baz = ::foo::bar::baz; . } } // namespace librarian • 禁止用内联命名空间 2.3.2 2.2. 匿名命名空间和静态变量 Tip: 在 .cc 文件中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为 static 。但是不要在 .h 文件中这么做。 定义: 所有置于匿名命名空间的声明都具有内部链接性,函数和变量可以经由声明为 static 拥有 内部链接性,这意味着你在这个文件中声明的这些标识符都不能在另一个文件中被访问。即 使两个文件声明了完全一样名字的标识符,它们所指向的实体实际上是完全不同的。 结论: 推荐、鼓励在 .cc 中对于不需要在其他地方引用的标识符使用内部链接性声明,但是不要在 .h 中使用。 匿名命名空间的声明和具名的格式相同,在最后注释上 namespace : 12 Chapter 2. C++ 风格指南 - 内容目录