本文主要是介绍C++代码规范 头文件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1. 头文件
通常每个 .cc
文件应该有一个配套的 .h
文件. 常见的例外情况包括单元测试和仅有 main()
函数的 .cc
文件.
正确使用头文件会大大改善代码的可读性和执行文件的大小、性能.
下面的规则将带你规避头文件的各种误区.
1.1. 自给自足的头文件
Tip
头文件应该自给自足 (self-contained, 也就是可以独立编译), 并以 .h
为扩展名. 给需要被导入 (include) 但不属于头文件的文件设置为 .inc
扩展名, 并尽量避免使用.
所有头文件应该自给自足, 也就是头文件的使用者和重构工具在导入文件时无需任何特殊的前提条件. 具体来说, 头文件要有头文件防护符 (header guards, 1.2. #define 防护符),并导入它所需的所有其它头文件.
若头文件声明了内联函数 (inline function) 或模版 (template), 而且头文件的使用者需要实例化 (instantiate) 这些组件时, 头文件必须直接或通过导入的文件间接提供这些组件的实现 (definition). 不要把这些实现放到另一个头文件里 (例如 -inl.h
文件) 再导入进来; 这是过去的惯例, 但现在被禁止了. 若模版的所有实例化过程都仅出现在一个 .cc
文件中, 比如采用显式 (explicit) 实例化, 或者只有这个 .cc
文件会用到模版定义 (definition), 那么可以把模版的定义放在这个文件里.
在少数情况下, 用于导入的文件不能自给自足. 它们通常是要在特殊的地方导入, 例如另一个文件中间的某处. 此类文件不需要使用头文件防护符, 也不需要导入它的依赖 (prerequisite). 此类文件应该使用 .inc
扩展名. 尽量少用这种文件, 可行时应该采用自给自足的头文件.
1.2. #define 防护符
Tip
所有头文件都应该用 #define
防护符来防止重复导入. 防护符的格式是: <项目>_<路径>_<文件名>_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
若代码文件或头文件引用了其他地方定义的符号 (symbol), 该文件应该直接导入 (include) 提供该符号的声明 (declaration) 或者定义 (definition) 的头文件. 不应该为了其他原因而导入头文件.
不要依赖间接导入. 这样, 人们删除不再需要的 #include
语句时, 才不会影响使用者. 此规则也适用于配套的文件: 若 foo.cc
使用了 bar.h
的符号, 就需要导入 bar.h
, 即使 foo.h
已经导入了 bar.h
.
1.4. 前向声明
Tip
尽量避免使用前向声明. 应该 导入你所需的头文件。
定义:
前向声明 (forward declaration) 是没有对应定义 (definition) 的声明.
// 在 C++ 源码文件中: class B; void FuncInB(); extern int variable_in_b; ABSL_DECLARE_FLAG(flag_in_b);
优点:
使用前向声明能节约编译时间, 因为
#include
会迫使编译器打开更多的文件并处理更多的输入.使用前向声明能避免不必要的重复编译. 若使用
#include
, 头文件中无关的改动也会触发重新编译.
缺点:
前向声明隐藏了依赖关系, 可能会让人忽略头文件变化后必要的重新编译过程.
相比
#include
, 前向声明的存在会让自动化工具难以发现定义该符号的模块.修改库 (library) 时可能破坏前向声明. 函数或模板的前向声明会阻碍头文件的负责人修改 API, 例如拓宽 (widening) 参数类型, 为模版参数添加默认值, 迁移到新的命名空间等等, 而这些操作本是无碍的.
为
std::
命名空间的符号提供前向声明会产生未定义行为 (undefined behavior).很难判断什么时候该用前向声明, 什么时候该用
#include
. 用前向声明代替#include
时, 可能会悄然改变代码的含义:// b.h: struct B {}; struct D : B {};// good_user.cc: #include "b.h" void f(B*); void f(void*); void test(D* x) { f(x); } // 调用 f(B*)若用
B
和D
的前向声明替代#include
,test()
会调用f(void*)
.
为多个符号添加前向声明比直接
#include
更冗长.为兼容前向声明而设计的代码 (比如用指针成员代替对象成员) 更慢更复杂.
结论:
尽量避免为其他项目定义的实体提供前向声明.
1.5. 内联函数¶
Tip
只把 10 行以下的小函数定义为内联 (inline).
定义:
你可以通过声明建议编译器展开内联函数, 而不是使用正常的函数调用机制.
优点:
只要内联函数体积较小, 内联函数可以令目标代码 (object code) 更加高效. 我们鼓励对存取函数 (accessors)、变异函数 (mutators) 和其它短小且影响性能的函数使用内联展开.
缺点:
滥用内联将拖慢程序. 根据函数体积, 内联可能会增加或减少代码体积. 通常, 内联展开非常短小的存取函数会减少代码大小, 但内联一个巨大的函数将显著增加代码大小. 在现代处理器上, 通常代码越小执行越快, 因为指令缓存利用率高.
结论:
合理的经验法则是不要内联超过 10 行的函数. 谨慎对待析构函数. 析构函数往往比表面上更长, 因为会暗中调用成员和基类的析构函数!
另一个实用的经验准则: 内联那些有循环或
switch
语句的函数通常得不偿失 (除非这些循环或switch
语句通常不执行).应该注意, 即使函数被声明为内联函数, 也不一定真的会被内联; 比如, 虚函数和递归函数通常不会被内联. 一般不应该声明递归函数为内联. (YuleFox 注: 递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 为虚函数声明内联的主要目的是在类 (class) 中定义该函数, 以便于使用该函数或注释其行为. 这常用于存取函数和变异函数.
1.6. #include
的路径及顺序
Tip
推荐按照以下顺序导入头文件: 配套的头文件, C 语言系统库头文件, C++ 标准库头文件, 其他库的头文件, 本项目的头文件.
头文件的路径应相对于项目源码目录, 不能出现 UNIX 目录别名 (alias) .
(当前目录) 或 ..
(上级目录). 例如, 应该按如下方式导入 google-awesome-project/src/base/logging.h
:
#include "base/logging.h"
在 dir/foo.cc
或 dir/foo_test.cc
这两个实现或测试 dir2/foo2.h
内容的文件中, 按如下顺序导入头文件:
dir2/foo2.h
.空行
C 语言系统文件 (确切地说: 用使用方括号和
.h
扩展名的头文件), 例如<unistd.h>
和<stdlib.h>
.空行
C++ 标准库头文件 (不含扩展名), 例如
<algorithm>
和<cstddef>
.空行
其他库的
.h
文件.空行
本项目的
.h
文件.
每个非空的分组之间用空行隔开.
这种顺序可以确保在 dir2/foo2.h
缺少必要的导入时, 构建 (build) dir/foo.cc
或 dir/foo_test.cc
会失败. 这样维护这些文件的人会首先发现构建失败, 而不是维护其他库的无辜的人.
dir/foo.cc
和 dir2/foo2.h
通常位于同一目录下 (如 base/basictypes_unittest.cc
和 base/basictypes.h
), 但有时也放在不同目录下.
注意 C 语言头文件 (如 stddef.h
) 和对应的 C++ 头文件 (cstddef
) 是等效的. 两种风格都可以接受, 但是最好和现有代码保持一致.
每个分组内部的导入语句应该按字母序排列. 注意旧代码可能没有遵守这条规则, 应该在方便时进行修正.
举例来说, google-awesome-project/src/foo/internal/fooserver.cc
的导入语句如下:
#include "foo/server/fooserver.h"#include <sys/types.h> #include <unistd.h>#include <string> #include <vector>#include "base/basictypes.h" #include "foo/server/bar.h" #include "third_party/absl/flags/flag.h"
例外:
有时平台相关的 (system-specific) 代码需要有条件地导入 (conditional include),此时可以在其他导入语句后放置条件导入语句. 当然, 尽量保持平台相关的代码简洁且影响范围小. 例如:
#include "foo/public/fooserver.h"#include "base/port.h" // 为了 LANG_CXX11.#ifdef LANG_CXX11 #include <initializer_list> #endif // LANG_CXX11
这篇关于C++代码规范 头文件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!