Google C++ Style Guide 谷歌 C++编码风格指南


Google C++ Style Guide 谷歌 C++编码风格指南

1. 头文件 — Google 开源项目风格指南 (zh-google-styleguide.readthedocs.io)

( 以下是2017版本的,要参考的朋友直接点击下面的链接)

Google C++ Style Guide 谷歌 C++编码风格指南


0. 扉页

版本 4.45

原作者
Benjy Weinberger, Craig Silverstein, Gregory Eitzmann, Mark Mentovai, Tashana Landray

翻译
YuleFox, Yang.Y, acgtyrant, lilinsanity

项目主页

• Google Style Guide
• Google 开源项目风格指南- 中文版

   

0.1 译者前言


Google 经常会发布一些开源项目, 意味着会接受来自其他代码贡献者的代码. 但是如果代码贡献者的编程风格与

Google 的不一致, 会给代码阅读者和其他代码提交者造成不小的困扰. Google 因此发布了这份自己的编程风格指南,

翻译初衷:

    规则的作用就是避免混乱. 但规则本身一定要权威, 有说服力, 并且是理性的. 我们所见过的大部分编程规
    
范, 其内容或不够严谨, 或阐述过于简单, 或带有一定的武断性.

Google 保持其一贯的严谨精神, 5 万汉字的指南涉及广泛, 论证严密. 我们翻译该系列指南的主因也正是

其严谨性. 严谨意味着指南的价值不仅仅局限于它罗列出的规范, 更具参考意义的是它为了列出规范而做

的谨慎权衡过程.

指南不仅列出你要怎么做, 还告诉你为什么要这么做, 哪些情况下可以不这么做, 以及如何权衡其利弊. 其

他团队未必要完全遵照指南亦步亦趋, 如前面所说, 这份指南是Google 根据自身实际情况打造的, 适用于

其主导的开源项目. 其他团队可以参照该指南, 或从中汲取灵感, 建立适合自身实际情况的规范.

我们在翻译的过程中, 收获颇多. 希望本系列指南中文版对你同样能有所帮助.

我们翻译时也是尽力保持严谨, 但水平所限, bug 在所难免. 有任何意见或建议, 可与我们取得联系.

中文版和英文版一样, 使用Artistic License/GPL 开源许可.

中文版修订历史:

• 2015-08 : 热心的清华大学同学@lilinsanity 完善了「类」章节以及其它一些小章节。至此,对Google CPP

Style Guide 4.45 的翻译正式竣工。

• 2015-07 4.45 : acgtyrant 为了学习C++ 的规范,顺便重新翻译了本C++ 风格指南,特别是C++11 的全新
   
内容。排版大幅度优化,翻译措辞更地道,添加了新译者笔记。Google 总部C++ 工程师innocentim, 清华大

学不愿意透露姓名的唐马儒先生,大阪大学大学院情报科学研究科计算机科学专攻博士farseerfc 和其它Arch

Linux 中文社区众帮了译者不少忙,谢谢他们。因为C++ Primer 尚未完全入门,暂时没有翻译「类」章节和

其它一些小章节。

• 2009-06 3.133 : YuleFox 的1.0 版已经相当完善, 但原版在近一年的时间里, 其规范也发生了一些变化.
   
Yang.Y 与YuleFox 一拍即合, 以项目的形式来延续中文版: Google 开源项目风格指南- 中文版项目.

主要变化是同步到3.133 最新英文版本, 做部分勘误和改善可读性方面的修改, 并改进排版效果.

Yang.Y 重新翻修, YuleFox 做后续评审.


• 2008-07 1.0 : 出自YuleFox 的Blog, 很多地方摘录的也是该版本.  

 
     
0.2 背景

    C++ 是Google 大部分开源项目的主要编程语言. 正如每个C++ 程序员都知道的, C++ 有很多强大的特性, 但
    
这种强大不可避免的导致它走向复杂,使代码更容易产生bug, 难以阅读和维护.


本指南的目的是通过详细阐述C++ 注意事项来驾驭其复杂性. 这些规则在保证代码易于管理的同时, 也能高效

使用C++ 的语言特性.

风格, 亦被称作可读性, 也就是指导C++ 编程的约定. 使用术语“风格” 有些用词不当, 因为这些习惯远不止源代

文件格式化这么简单.


使代码易于管理的方法之一是加强代码一致性. 让任何程序员都可以快速读懂你的代码这点非常重要. 保持统一

编程风格并遵守约定意味着可以很容易根据“模式匹配” 规则来推断各种标识符的含义. 创建通用, 必需的习惯用语

和模式可以使代码更容易理解. 在一些情况下可能有充分的理由改变某些编程风格, 但我们还是应该遵循一致性原则,


尽量不这么做.


本指南的另一个观点是C++ 特性的臃肿. C++ 是一门包含大量高级特性的庞大语言. 某些情况下, 我们会限制

甚至禁止使用某些特性. 这么做是为了保持代码清爽, 避免这些特性可能导致的各种问题. 指南中列举了这类特性, 并

解释为什么这些特性被限制使用.

Google 主导的开源项目均符合本指南的规定.

注意: 本指南并非C++ 教程, 我们假定读者已经对C++ 非常熟悉.


1. 头文件

    通常每一个.cc 文件都有一个对应的.h 文件. 也有一些常见例外, 如单元测试代码和只包含main() 函数的.cc
       
文件.
    正确使用头文件可令代码在可读性、文件大小和性能上大为改观.
       
    下面的规则将引导你规避使用头文件时的各种陷阱.
       
       
1.1. Self-contained 头文件       

____________________________________________________________________________

Tip: 头文件应该能够自给自足(self-contained, 也就是可以作为第一个头文件被引入),以.h 结尾。至于用来插入

文本的文件,说到底它们并不是头文件,所以应以.inc 结尾。不允许分离出-inl.h 头文件的做法.
____________________________________________________________________________

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

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

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

曾提倡把定义放到-inl.h 里过)。

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

1.2. #define 保护

___________________________________________________________________________

Tip: 所有头文件都应该使用#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. 前置声明
 

___________________________________________________________________________

Tip: 尽可能地避免使用前置声明。使用#include 包含需要的头文件即可。___________________________________________________________________________

定义:

    所谓「前置声明」(forward declaration)是类、函数和模板的纯粹声明,没伴随着其定义.
    
优点:
    • 前置声明能够节省编译时间,多余的#include 会迫使编译器展开更多的文件,处理更多的输入。

    • 前置声明能够节省不必要的重新编译的时间。#include 使代码因为头文件中无关的改动而被重新编译多次。
缺点:
    • 前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。

    • 前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其API.

           例如扩大形参类型,加个自带默认参数的模板形参等等。
        
    • 前置声明来自命名空间std:: 的symbol 时,其行为未定义。

    • 很难判断什么时候该用前置声明,什么时候该用#include 。极端情况下,用前置声明代替

 
includes 甚至都会暗暗地改变代码的含义:

如果#include 被B 和D 的前置声明替代,test() 就会调用f(void*) . * 前置声明了不少来自头文件的

symbol 时,就会比单单一行的include 冗长。* 仅仅为了能前置声明而重构代码(比如用指针成员代替

对象成员)会使代码变得更慢更复杂.

结论:

• 尽量避免前置声明那些定义在其他项目中的实体.

• 函数:总是使用#include.

• 类模板:优先使用#include.

至于什么时候包含头文件,参见name-and-order-of-includes。


1.4 内联函数

___________________________________________________________________________

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

___________________________________________________________________________

定义:
    当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用.
    
优点:
    只要内联的函数体较小, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体比较短, 性能
    
关键的函数, 鼓励使用内联.

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


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

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

这些循环或switch 语句从不被执行).

有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就不会被正常

内联. 通常, 递归函数不应该声明成内联函数.(YuleFox 注: 递归调用堆栈的展开并不像循环那么简单, 比

如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 虚函数内联的主要原因则是想

把它的函数体放在类定义内, 为了图个方便, 抑或是当作文档描述其行为, 比如精短的存取函数.

1.5. #include 路径及其顺序
 

___________________________________________________________________________

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

的.h.

___________________________________________________________________________

    项目内头文件应按照项目源代码目录树结构排列, 避免使用UNIX 特殊的快捷目录. (当前目录) 或.. (上级目

录). 例如, google-awesome-project/src/base/logging.h 应该按如下方式包含:

    #include "base/logging.h"

    又如, dir/foo.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-declaration 情况除
    
外。比如您要用到bar.h 中的某个symbol, 哪怕您所包含的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" // 优先位置

#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

译者 (YuleFox) 笔记

1. 避免多重包含是学编程时最基本的要求;

2. 前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;

3. 内联函数的合理使用可提高代码执行效率;

4. -inl.h 可提高代码可读性(一般用不到吧:D);

5. 标准化函数参数顺序可以提高可读性和易维护性(对函数参数的堆栈空间有轻微影响, 我以前大多是相同类型放

在一起);

6. 包含文件的名称使用. 和.. 虽然方便却易混乱, 使用比较完整的项目路径看上去很清晰, 很条理, 包含文件的

次序除了美观之外, 最重要的是可以减少隐藏依赖, 使每个头文件在“最需要编译” (对应源文件处:D) 的地方编

译, 有人提出库文件放在最后, 这样出错先是项目内的文件, 头文件都放在对应源文件的最前面, 这一点足以保证

内部错误的及时发现了.


译者 acgtyrant笔记

1. 原来还真有项目用#includes 来插入文本,且其文件扩展名.inc 看上去也很科学。

2. Google 已经不再提倡-inl.h 用法。

3. 注意,前置声明的类是不完全类型(incomplete type),我们只能定义指向该类型的指针或引用,或者声明(但

不能定义)以不完全类型作为参数或者返回类型的函数。毕竟编译器不知道不完全类型的定义,我们不能创建

其类的任何对象,也不能声明成类内部的数据成员。

4. 类内部的函数一般会自动内联。所以某函数一旦不需要内联,其定义就不要再放在头文件里,而是放到对应的

.cc 文件里。这样可以保持头文件的类相当精炼,也很好地贯彻了声明与定义分离的原则。

5. 在#include 中插入空行以分割相关头文件, C 库, C++ 库, 其他库的.h 和本项目内的.h 是个好习惯。


2. 作用域

2.1. 名子空间
 

___________________________________________________________________________

Tip: 鼓励在.cc 文件内使用匿名名字空间. 使用具名的名字空间时, 其名称可基于项目名或相对路径. 禁止使用

using 指示(using-directive)。禁止使用内联命名空间(inline namespace)。___________________________________________________________________________

定义:

    名字空间将全局作用域细分为独立的, 具名的作用域, 可有效防止全局作用域的命名冲突.
    
优点:

    虽然类已经提供了(可嵌套的)命名轴线(YuleFox 注: 将命名分割在不同类的作用域内), 名字空间在这
    
    基础上又封装了一层.
    
    举例来说, 两个不同项目的全局作用域都有一个类Foo, 这样在编译或运行时造成冲突. 如果每个项目将代
    
    码置于不同名字空间中, project1::Foo 和project2::Foo 作为不同符号自然不会冲突.
    
    内联命名空间会自动把内部的标识符放到外层作用域,比如:


namespace X {

inline namespace Y {

void foo();

}

}

    X::Y::foo() 与X::foo() 彼此可代替。内联命名空间主要用来保持跨版本的ABI 兼容性。


缺点:

名字空间具有迷惑性, 因为它们和类一样提供了额外的(可嵌套的) 命名轴线.

命名空间很容易令人迷惑,毕竟它们不再受其声明所在命名空间的限制。内联命名空间只在大型版本控制

里有用。

在头文件中使用匿名空间导致违背C++ 的唯一定义原则(One Definition Rule (ODR)).

结论:

    根据下文将要提到的策略合理使用命名空间.


未完待续......

 ( 以上是2017版本的,要参考的朋友直接点击下面的链接)

Google C++ Style Guide 谷歌 C++编码风格指南

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值