您的位置:首页 > 编程语言 > C语言/C++

Google C++ 编程风格指南(1):头文件(1)

2011-09-30 13:24 281 查看
Google C++ 编程风格指南(1):头文件(1)

2008年07月11日 |  C/C++, Google, 程序设计 占个座先

Google C++ 编程风格指南(1):头文件(1)

#define 保护

头文件依赖

内联函数

通常来讲,每一个 .cc 文件都应该有一个与之关联的 .h 文件,不过有一些例外,比方说单元测试和小到只拥有一个 main() 函数的 .cc 文件。

正确使用头文件可以大大改善代码的可读性、长度和性能。

下面的准则将指引你跨越使用头文件时各种各样的陷阱。

#define 保护

所有的头文件都应该使用 #define 宏来防止被多次包含,书写宏的格式应该是 PROJECT_PATH_FILE_H_。

为了确保唯一性,#define 宏应该根据项目源文件树的完整路径书写。例如,项目 foo 中的 foo/src/bar.baz.h 应该使用下面的宏:

#ifndef FOO_BAR_BAZ_H_

#define FOO_BAR_BAZ_H_

...

#endif // FOO_BAR_BAZ_H_

头文件依赖

使用前向声明来最小化头文件中 #include 的使用次数。

包含头文件来引入一个依赖,将会导致你的代码在头文件发生变化时需要重新编译。如果你的头文件包含了其它的一些头文件,当那些头文件发生任何改变后将会导致包含你的头文件的代码需要重新编译(绕口令吗?)。因此,我们建议最小化包含,尤其是在头文件中包含其它的头文件。

使用前向声明,你能够立杆见影地最小化你的头文件中需要包含的头文件的数量。举例来说,如果你的头文件使用了 File 类,但是不需要访问 File 类的定义(原文是 declaration,八成是笔误),那么你可以在你的头文件只对 File 类进行前向声明,而不是 #include "file/base/file.h"。

我们在什么情况下才能在头文件中使用 Foo 类而不需要 Foo 类的定义?

声明数据成员的类型为 Foo* 或 Foo&。

使用 Foo 作为函数的参数、(和/或)返回值。

声明类型为 Foo 的静态数据成员,因为静态数据成员的定义在类定义之外。

与之相反,当你的类是 Foo 派生的子类或拥有一个类型为 Foo 的数据成员,必须在头文件中包含 Foo 的头文件。

有时使用指针成员比对象成员更有意义。尽管如此,这将削弱代码的可读性和性能,如果唯一的目的只是最小化头文件的包含数时,避免对象到指针的转换。

当然,.cc 文件一般必须要有它们所使用的类的定义,所以通常都会包含数个头文件。

内联函数

仅当函数很小,比方说只有 10 行或更少时,才将它们声明了内联。

内联函数的定义:将函数声明为内联将允许编译器在行内展开函数,而不是通过通常的函数调用机制对它们进行调用。

好处:只要这些函数很小,内联就能够产生更加高效的代码。放心地将 accessors 和 mutators 声明为内联吧,还有其它一些性能至上的函数。

坏处:滥用内联将会导致程序更慢。根据一个函数的大小,内联会导致代码的尺寸增大或减小。通常地,让一个非常小的 accessor 函数内联会减小代码的体积,然而让一个非常大的函数内联将会显著地增大代码的体积。现代的处理器通常运行小代码会更快,因为可以充分利用缓存。

如何决定是否使用内联

一个体面的准则是:当一个函数超过 10 行就不要内联。当心析构函数,它们往往比看上去更长,因为它们会隐式调用成员或基类的析构函数!

另一个有用的准则是:对拥有循环或 switch 语句的函数使用内联并不会使之更高效(除非循环或 switch 语句永远不会被执行)。

还有一点很重要:即使一个函数被声明为内联也不见得它就真的是内联。例如:虚函数和递归函数一般都不会内联。通常来讲,递归函数就不应该内联。使一个虚函数内联的主要原因是将它的定义放在类里面,不仅方便而且易于表示它的行为,比如 accessors 和 mutators。

Google C++ 编程风格指南(1):头文件(2)

-inl.h 文件

函数的参数顺序

头文件名称的顺序

-inl.h 文件

当需要的时候,你应该使用带有 -inl.h 后缀的名字来定义复杂的内联函数。

内联函数的定义需要放在一个头文件里,使得编译器在发生函数调用的地点展开函数定义。尽管如此,实现代码出现在 .cc 文件中更为合适,我们不想让 .h 文件拥有太多的代码,除非那样做可以提高可读性或是性能。

如果一个内联函数的定义很短小,你应该将其放在 .h 文件中。例如,accessors 和 mutators 应该始终放在类定义之内。更加复杂的内联函数也应该放在一个 .h 文件内以方便实现和调用,尽管这样会造成 .h 文件太笨重。解决方法就是将这些代码放入一个单独的 -inl.h 文件内,虽然会导致它与类的定义相分离,但仍然允许实现部分在必要的时候被包含。

-inl.h 文件的另一个用法是定义模板函数,它用来保持你的模板定义容易被读懂。

不要忘记像任何其它头文件那样,给 -inl.h 写一个 #define 宏。

函数的参数顺序

定义一个函数时,参数顺序应该是:先输入、后输出。

C/C++ 函数的参数既可以是输入,也可以是输出,或者二者皆是。输入参数通常是值或 const 引用,输出参数通常是 non-const 指针。当为函数的参数排序时,应把所有只作为输入的参数放在任意输出参数前面。尤其不要把新添加的参数放在函数的最后仅仅因为它们是新添加的;把新添加的输入参数放在输出参数之前。

这不是一条死规矩。那些同时既是输入又是输出的参数(经常是类/结构体)会把水搅浑。所以,遇到这样的函数时,你可以不完全遵守这一条。

头文件名称的顺序

按标准顺序使用头文件会增强代码的可读性,并可避免引入隐式对 C 库、C++ 库或其它库的、你的库的头文件的依赖。

一个工程的所有头文件都应该放入工程源代码目录下,而不要使用 UNIX 目录的快捷方式:.(当前目录)或 ..(上级目录)。例如:google-awesome-project/src/base/logging.h 应该被这样包含:

#include "base/loggin.h"

在一个主要目的是实现或测试 dir2/foo2.h 的 dir/foo.cc 文件中,将头文件按以下顺序包含:

dir2/foo2.h(优先位置)

C 系统文件

C++ 系统文件

其它库的头文件

你的工程的头文件

优先位置会减少隐式依赖。我们希望每个头文件被分别编译,达到这个目的的最简单途径就是保证它们是某个 .cc 文件中包含的第一个 .h 文件。

dir/foo.cc 和 dir2/foo2.h 经常在相同的目录下(例: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/basictypes.h"

#include "base/commandlineflags.h"

#include "foo/public/bar.h"
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息