C++风格指南 1、头文件

 通常每个 .cc 文件应该有一个配套的 .h 文件. 单元测试和仅有 main().cc 文件是例外。 

1.1、使用头文件的好处

1. 自给自足的头文件:头文件应独立完整,包含所需的所有其他头文件和定义,无需特殊前提条件。

2. 头文件防护符:头文件应使用 `#define` 防护符来防止重复导入。

3. 内联函数和模板的实现:如果头文件声明了内联函数或模板,并且使用者需要实例化这些组件,头文件应提供相应的实现。不应将实现放在另一个头文件(如 `-inl.h`)中,这种做法已被禁止。

4. 显式实例化:如果模板的所有实例化都在一个 `.cc` 文件中,可以将模板定义放在该 `.cc` 文件中。

5. 特殊情况下的文件:有些用于导入文件可能不自给自足,通常用于特殊导入位置,这类文件不需要头文件防护符和导入依赖,应使用 `.inc` 扩展名,并尽量减少使用。

6. 推荐做法:在可行的情况下,应优先使用自给自足的头文件。
 

1.2、#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.3. 导入你的依赖

1. 直接导入:如果代码文件或头文件引用了其他位置定义的符号,应直接导入包含该符号声明或定义的头文件。

2. 避免间接导入:不应依赖于其他头文件间接提供的符号,以防止删除不再需要的 `#include` 语句时影响代码的使用者。

3. 适用性:此规则适用于所有情况,包括配套文件。例如,如果 `foo.cc` 使用了 `bar.h` 中的符号,即使 `foo.h` 已经包含了 `bar.h`,`foo.cc` 也应直接导入 `bar.h`。
 

1.4. 前向声明 

1. 避免前向声明:推荐直接导入所需的头文件,而不是使用前向声明。

2. 前向声明定义:前向声明是无对应定义的声明,例如类声明、函数声明等。

3. 优点:
   - 节省编译时间,因为#include会使编译器打开更多文件。
   - 避免因使用#include,导致头文件中无关改动引发的重复编译。

4. 缺点:
   - 隐藏依赖关系,可能导致忽略头文件变化后必要的重新编译
   - 相比 #include, 前向声明的存在会使自动化工具难发现定义符号的模块
   - 可能阻碍库的修改,如API变更、参数类型拓宽等。
   - 为 std:: 命名空间的符号前向声明可能导致未定义行为
   - 难以判断何时使用前向声明,可能改变代码含义。

  • // 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 的前向声明替代 #includetest() 会调用 f(void*) .

   - 替代 `#include` 可能导致调用错误的函数重载。
   - 为多个符号前向声明可能比直接 `#include` 更冗长。
   - 为兼容前向声明设计的代码可能更慢更复杂。

5. 结论:应尽量避免使用前向声明,尤其是在其他项目定义的实体上。

1.5. 内联函数

1. 内联函数定义:内联函数是建议编译器展开不是使用正常函数调用的函数

2. 内联函数建议:只对10行以下的小函数使用内联。

3. 优点:
   - 内联小函数可以提高目标代码效率。
   - 推荐对存取函数、变异函数和其他短小且关键性能的函数使用内联。

4. 缺点:
   - 滥用内联可能导致程序变慢。
   - 内联可能增加或减少代码体积,但大函数内联会增加代码大小。

5. 结论:
   - 不要内联超过10行的函数。
   - 析构函数可能比看起来更长,因为它们可能隐式调用成员和基类的析构函数。
   - 内联含循环或 `switch` 语句的函数通常不划算,除非这些结构很少执行。

6. 其他注意事项:
   - 即使声明为内联,编译器也可能不执行内联,特别是虚函数和递归函数
   - 递归函数通常不应声明为内联,因为编译器通常不支持。
   - 虚函数内联的主要目的是为了类内定义和注释行为,适用于存取和变异函数。
 

1.6. #include 的路径及顺序

1. 头文件导入顺序:推荐按照以下顺序导入头文件:
   - 配套的头文件
   - C 语言系统库头文件
   - C++ 标准库头文件
   - 其他库的头文件
   - 本项目的头文件

2. 头文件路径规范:头文件路径应相对于项目源码目录,避免使用 `.` 或 `..`。

3. 导入示例:例如,导入 `google-awesome-project/src/base/logging.h` 应使用 `#include "base/logging.h"`。

4. 文件实现或测试:在 `dir/foo.cc` 或 `dir/foo_test.cc` 中导入 `dir2/foo2.h` 时,应遵循上述顺序,并在每个非空分组之间用空行隔开。

5. 构建失败提示:这种顺序确保了如果 `dir2/foo2.h` 缺少必要导入,构建 `dir/foo.cc` 或 `dir/foo_test.cc` 会失败,从而让维护者及时发现问题。

6. 头文件位置:`dir/foo.cc` 和 `dir2/foo2.h` 通常位于同一目录,但也可能不同。

7. C/C++ 头文件等效性:C 语言头文件(如 `stddef.h`)和对应的 C++ 头文件(如 `cstddef`)是等效的,应与现有代码风格保持一致。

8. 导入语句排序:每个分组内部的导入语句应按字母序排列,旧代码应适时修正。

9. 导入语句示例:给出了 `google-awesome-project/src/foo/internal/fooserver.cc` 的导入语句顺序。

10. 平台相关代码:平台相关的代码可能需要有条件地导入,这应在其他导入语句后进行,尽量保持简洁且影响范围小。

11. 条件导入示例:给出了基于 `LANG_CXX11` 条件导入 `initializer_list` 的示例。
 

1.7后记 

有人提出库文件放在最后, 这样出错先是项目内的文件, 头文件都放在对应源文件的最前面, 这一点足以保证内部错误的及时发现了.

类内部的函数一般会自动内联。所以某函数一旦不需要内联,其定义就不要再放在头文件里,而是放到对应的 .cc 文件里。这样可以保持头文件的类相当精炼,也很好地贯彻了声明与定义分离的原则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值