`
lovecontry
  • 浏览: 1033786 次
文章分类
社区版块
存档分类
最新评论

Felomeng翻译:Google C++ 编程规范——头文件

 
阅读更多

与官方翻译版本(http://code.google.com/p/zh-google-styleguide/downloads/list)不同,本文为本人原创翻译。

一般地,.cc[1]文件都有一个对应的.h文件。但是有一些常见的例外情况,比如单元测试和只含有main()函数的小型源文件。

头文件的正确运用,可以极大地提高代码的可读性,控制代码的规模和提高软件的性能。

下列规则有助于避免头文件使用中容易产生的诸多错误。

1.1 利用 #define防止多重包含

头文件应该使用#define定义预编译标识符来标识当前头文件,以来避免多重包含。标识符命名规则是:[项目名]_[路径名]_[文件名]_H_

为了保证唯一性,标识符应当以头文件在项目中的全路径来命令。例如,在项目foo中的文件foo/src/bar/baz.h应该通过下面代码来进行保护:

#ifndef FOO_BAR_BAZ_H_

#define FOO_BAR_BAZ_H_

...

#endif // FOO_BAR_BAZ_H_

1.2 头文件依赖

可以使用前置声明时,就不要使用#include

源代码文件中包含一个头文件就形成了一个新的依赖关系,一旦修改此头文件,就得重新编译源代码文件。如果某个头文件中包含了别的头文件,那么修改任何一个头文件,都得重新编译包含这个头文件的源代码文件。因此,要尽量减少包含关系,尤其是头文件的相互包含。

在头文件中使用前置声明,可以有效减少头文件中包含头文件的数量。例如,头文件中要使用File类,但是却用不到File类的声明。这时可以不包含File类的头文件(#include “file/base/file.h”),只要使用前置声明来声明类File就可以了。

那么,怎样才能在不使用类Foo的定义就可以使用类Foo呢?可以这样:

声明数据成员类型Foo*Foo&

声明类Foo的函数及其参数,甚至包括返回值。(这里有一个例外,就是当变量Fooconst Foo&具有单参数隐式转换的构造函数时,就需要类的完整声明来支持自动类型转换)

声明类Foo的静态数据成员变量。因为静态数据成员的定义不包含在类定义之内。

但是,如果你要继承Foo类或者有成员变量是Foo类型,则必须引用Foo类的头文件。

有时,可以使用指针(scoped_ptr更佳)来代替对象作为成员变量。但是,这将影响代码可读性并降低程序执行效率。因此,如果仅是出于减少包含头文件数量的考虑,就不要这么做了。

当然,.cc文件通常需要所有类的定义,因此需要包含多个头文件。

提示:如果要在在源代码文件中使用Foo,则应当主动通过#include或前置声明引入Foo的定义。不要依赖于传递关系包含的头文件。例外情况:如果Foomyfile.cc文件中使用,则可以在myfile.h文件(而不是文件myfile.cc.)中包含(或前置声明)类Foo

1.3 内联函数

只有当函数体足够小时(不多于十行)才使用内联函数。

定义:内联函数与普通函数不同,不需要经过函数调用机制来调用。在编译内联函数时,编译器是先把内联函数体内的代码拷贝到调用内联函数的位置(这个过程称为内联展开),替换原有的调用语句,然后再编译。

优点:函数体较小时,使用内联方式可以提升程序执行效率。对于取值函数(accessor)[2]、赋值函数(mutator)[3],以及所有函数体短小而又要求高执行效率的函数,都应当声明为内联函数。

劣势:内联函数的滥用将导致程序运行缓慢。使用内联方式可能会增大或减小程序的体积,这与函数体本身的大小有关。将函数体很小的取值函数声明为内联方式,通常会减小程序体积;而将一个比较大的函数声明为内联方式,则将明显增大程序体积。另一方面,在现代处理器上,短代码运行效率优于老式处理器,因为现代处理器有更好的指令缓存机制(因此,无须使用内联函数)。

结论

一个比较好的规则就是当函数代码多于10行时,就不使用内联方式。要特别注意析构函数,它们的实际代码行数很可能大于看到的行数,因为析构函数中可能会隐含成员,还可能会包括父类的析构函数!

另一个好的规则是,具有循环体或分支(switch语句)结构的函数,不宜内联(除非这些循环或分支语句一般不会被执行)。

即使声明为内联函数,也不一定会被编译为内联函数,这一点很重要。比如虚函数和递归函数一般就不会编译为内联函数。通常,递归函数不应声明为内联函数。将虚函数声明为内联的主要是为了方便或者在文档中说明它的功能(比如取值和赋值函数)。

1.4 inl.h为后缀的文件

复杂内联函数,应当在以-inl.h为后缀的头文件中进行定义。

内联函数的定义需要放在头文件内,这样编译器才可以在调用处将其内联展开。但是,实现代码应当放在.cc文件当中,除非可以提高可读性或运行效率,否则不应该把太多实现代码放在.h文件中。

如果内联函数足够短小(只含有极少或是没有逻辑语句),则应该将内联函数放进.h文件。比如,取值函数和赋值函数就应当放在类的定义里面。为了实现和调用的方便,更复杂的内联函数也应当定义在.h文件中。如果这样做影响到了代码的可读性,那么可以将这些内联函数放进一个单独的头文件(以-inl.h为后缀)。这样可以把内联函数的实现与类定义分开,同时,又不影响在别处包含内联函数实现。

-inl.h为后缀的头文件的另一个用途是用来定义函数模板。这样做可以提高模板定义的可读性。

别忘了,与别的头文件一样,-inl.h为后缀的头文件同样应当利用 #define防止多重包含

1.5 函数参数排序

定义函数时,参数的顺序应为:输入参数在前,输出参数在后。

C/C++语言中函数的参数分为输入参数与输出参数,也可以既是输入又是输出参数(简称为输入/输出参数)。输入参数通常是值类型或者引用类型,而输出参数与输入/输出参数则为非指针。参数排序时,输入参数应排在其他参数前面。特别地,不能因为参数是新添加的而简单地将参数排到最后,要把新的输入参数排在输出参数前面。

这条规则并不要求严格遵守。因为输入/输出参数(通常是类或结构体)很特别,会影响到这条规则。有时为了保持与相关函数的一致性,则不得不违反这条规则。

1.6 文件包含的名称与次数

为了可读性并避免隐性依赖(hidden dependencies[4]),包含头文件的顺序应当遵循这样的标准:C库文件,C++库文件,其他的库文件.h,本工程中的库文件.h

项目中定义的头文件,都应该放在项目源文件夹中,并且包含语句中不应当含有UNIX文件夹缩写符号“.”(表示当前文件夹),或“..”(表示上一级文件夹)。例如,google-awesome-project/src/base/logging.h应当使用这样的包含语句:

#include "base/logging.h"

如果文件dir/foo.cc主要用来测试dir2/foo2.h,则应这样排列头文件包含顺序:

dir2/foo2.h(优先排序——下面会详细说明原因)

C库文件

C++库文件

其他.h库文件

当前项目.h库文件

优先排序可以减少隐性依赖。每个头文件都应该能独立编译。要达到这样的效果,最简单的方法就是让这些头文件作为.cc.文件中使用#include语句包含的第一个.h文件。

dir/foo.ccdir2/foo2.h通常在同一文件夹下(比如,base/basictypes_test.ccbase/basictypes.h),但也可以在不同的文件夹下。

每个库的包含语句,最好按字母顺序排序。

例如,google-awesome-project/src/foo/internal/fooserver.cc中的包含语句可以是这样的:

#include "foo/public/fooserver.h" // Preferred location.

#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"



[1] 译者注:Linux/UnixC++源代码文件的扩展名,与.cpp等同

[2] 译者注:accessor 原意是:函数名以get(获取)开头,以要获取的变量名结束,返回值的类型就是要获取变量的类型。故,此处译为取值函数

[3] 译者注:mutator原意是:可以修改某个成员变量值的函数,通常指通过一定计算修改某成员变量值的函数(多数情况下就是直接把某个值赋给成员变量)。其实,除了常函数外,所有函数都可以称为mutator。故,此处译为赋值函数。

[4] 译者注:隐性依赖是指两个看似相互独立的组件具有的某种关联,这种关联通常由第三方组件中的数据流引起。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics