Google C++ Style Guide 【译稿一】
时间:2011-03-12 来源:千羽鸣
这份译稿由千羽鸣([email protected])翻译,仅限学习交流目的,请勿用于其它用途!!
今天看到Google的《C++编码规范指南》,感到这份文档真是难得的极品,国内更是少有此类规范,花了小半天时间,只翻译了不到五页,现在此贴出来做个抢先版。
Google C++编码规范指南【前言、头文件、依赖以及内置函数部分】
前言
许多Google开源项目都主要使用C++开发, 不过每个C++开发者都应该知道, C++虽然有很多强大的特性, 这却也使得它更为复杂,使得代码更容易出现bug,更难于阅读和维护.
为了帮助大家控制C++的这种复杂性,指南描述了一些编码规范.这些规范可以帮助编码人员在保证生产效率的同时保证代码的可维护性.
编码风格有时候也被等同于代码的可读性,也就是我们通常用于管理我们那些C++代码的惯例.只是”风格”一词显得不是那么正式,因为”惯例”的范围毕竟不仅仅是源代码的格式.
一种保证代码可维护性的方法是强制要求”连贯性”.即团队中任何一个编码人员都应该能够很快看懂别人的代码.通过使用一种统一的风格及编码规范可以使我们很容易通过模式匹配获知各种符号是什么以及其用途. 通过创制一些必要的惯用法以及编码模式,,可以使得代码更具可读性. 在某些情况下可能我们有充分的理由更改某些风格规则,但是为了保证代码的连贯性,我们仍然应该遵循约定.
另外一个指南中要讨论的问题是C++某些臃肿的机制和特性.C++有很多先进的特性. 然而有时我们会限制甚至禁用某些特性.,因为这样可以使代码简洁,可以避免这些特性会带来的各种错误和问题.这份指南中将会列出这些特性,并解释为何它们应该受到限制.
Google的开源项目都是依照本指南所述的标准开发的.需要注意的是本指南并非C++入门资料,我们在这里假设读者已经熟悉C++语言.
头文件
通常每个.cc文件(C++源文件有.cpp,.cxx,.cc等,cc是其中一种)都应该有一个相应的.h头文件。不过也有一些常见的例外情况,譬如单元测试源文件,以及只包含一个main()函数的.cc文件。
正确地使用头文件可以使代码的可读性、体积以及性能变得截然不同.
下面的规则会知道你避开各种使用头文件的不良习惯.
防止文件重复包含的#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_
依赖头文件
如果前面的声明已经满足了依赖性,就没有必要再来一个#include.
当你在一个源文件中包含某个头文件,那么根据make规则,任何该头文件的改变都会导致源文件的重编译。如果你的头文件又包含了其它的头文件,那么那些头文件的改变也同样会导致源文件的重新编译。因此,我们应当尽量减少包含,尤其是头文件中的包含指令。
你可以通过前置声明来有效地减少头文件的包含吗。譬如你有个头文件用到了File 类,却无需访问其实现,那么我们就可以前置声明class File; 用不着 #include "file/base/file.h".
什么情况下我们可以在一个头文件中使用类 Foo 而无须包含它的定义呢?
· 我们可以将其定义为指针或引用,如 Foo* 或 Foo&.
· 我们可以声明(而不是定义)带参数以及Foo类型返回值的函数,不过也有例外,如果一个参数Foo 或 const Foo& 有一个默认的带参构造函数,那么我们就需要它的全部定义以进行自动类型转换.) -?-
· 我们可以定义Foo 类型的静态成员变量. 因为静态成员变量是在类体之外定义的.
在其它情况下,如果你的类以Foo 类型为子类或有一个Foo类型的头文件,就必须包含相应的头文件了。
有时定义一个指针成员(或者有作用域的成员)来代替对象成员,这样可以减少头文件的饿包含。然而这样做的代价是降低程序的可读性以及程序的性能,所以如果仅仅是想减少头文件的包含,就没有必要这样做。
当然,.cc源文件通常必须要有它们使用的类的定义,也通常会包含好几个头文件。
注意: 如果你在源文件中使用 Foo, 那么你自己应该在源文件中引入 Foo 的定义, 譬如通过包含头文件或进行前置声明. 尽量不要依赖那些没有直接包含的头文件中获取符号定义. 例如, myfile.cc 中需要使用Foo,那么通过#include包含myfile.h以使用其中关于Foo的定义,这是没问题的,
内置函数(Inline Functions)
只有在函数代码很少的情况下将其定义为内置函数,譬如一个只有10行甚至更少的函数。
定义: 如果你希望编译器能够直接将函数代码插入到调用位置而不是通过普通的函数调用机制,那么你就可以使用内置函数。
优点:函数比较小的情况下,使用内置函数可以使得目标代码变得更为高效。对于一些对象属性存取以及修改函数,你完全可以将其设置为内置函数。
缺点:事实上,过度使用内置函数会使程序变得更为缓慢。在程序体积方面,内置函数会使代码体积增大或减小。将一个很小的存取函数声明为内置的通常会减少代码体积,然而将一个很大的函数声明称内置的会使的代码体积灾难性扩大。在现代处理器上,由于小的代码片段通常可以更好地利用指令缓存器,它们可以运行的更快。
取舍:
一种恰当的做法是不内置超过十行的代码。需要注意的是析构函数,因为他们经常要比它们看起来的长得多,它们不但要有许多没有明确写出的成员的析够调用,还有基类的析够调用!
另外一种有用的规则:通常将带有循环体或者switch声明的函数声明称内置函数开销不会很大(除非这个循环体或者switch声明从来都不会被执行).
非常重要的一点就是即使是声明成内置函数,函数通常都不会真正被内置,譬如虚函数和递归函数正常情况下都是不会被内置的。通常递归函数不应该被内置。为了将一个虚函数的定义放到类体中,为了方便,或者便于文档化其行为,譬如对象访存及属性修改。