1 头文件的布局: CPP面向对象-头文件_#include2 #define 保护

所有头文件都应该有 #define 保护来防止头文件被多重包含, 命名格式当是: <PROJECT>_<PATH>_<FILE>_H_

防御式开头防止重复include头文件。

#ifndef COMMON_H
#define COMMON_H
//.... code
#endif //COMMON_H

Google C++规范:

为保证唯一性, 头文件的命名应该基于所在项目源代码树的全路径。

例如, 项目 foo 中的头文件 foo/src/bar/baz.h 可按如下方式保护:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

也可以使用

#pragma once
3 前置声明

尽可能地避免使用前置声明。使用 #include 包含需要的头文件即可。

前置声明(forward declaration)是类、函数和模板的纯粹声明,没伴随着其定义。

前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应。

Google C++规范:

优点:

  • 节省编译时间:多余的 #include 会迫使编译器展开更多的文件,处理更多的输入。
  • 节省不必要的重新编译的时间: #include 使代码因为头文件中无关的改动而被重新编译多次。

缺点:

  • 前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。

  • 前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其 API. 例如扩大形参类型,加个自带默认参数的模板形参等等。

  • 极端情况下,用前置声明代替 #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); }  // calls f(B*)

​ 如果 #includeBD 的前置声明替代, test() 就会调用 f(void*)

结论:

  • 尽量避免前置声明那些定义在其他项目中的实体.
  • 函数:总是使用 #include.
  • 类模板:优先使用 #include.
4 内联函数

只有当函数只有 10 行甚至更少时才将其定义为内联函数.

当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用。

在类的声明中定义的函数将被编译器尝试翻译为内联函数。

优点:

只要内联的函数体较小, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联.

缺点:

滥用内联将导致程序变得更慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。

经验:

  1. 不要内联超过 10 行的函数
  2. 谨慎对待析构函数
  3. 内联包含循环或 switch 语句的函数往往得不偿失

一般不会被内联的函数:虚函数和递归函数不会被正常内联

5 #include 的路径及顺序

使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖: 相关头文件, C 库, C++ 库, 其他库的 .h, 本项目内的 .h.

项目内头文件应按照项目源代码目录树结构排列, 避免使用 UNIX 特殊的快捷目录 . (当前目录) 或 .. (上级目录).(即使用绝对路径而非相对路径)

#include 的顺序:

  1. dir2/foo2.h (这个cpp文件对应的.h文件,放置于优先位置)
  2. C 系统文件
  3. C++ 系统文件
  4. 其他库的 .h 文件
  5. 本项目内 .h 文件

这种优先的顺序排序保证 dir2/foo2.h(.h文件) 遗漏某些必要的库时, 其实现/测试(.cpp文件)的构建会立刻中止。这一条规则保证维护这些文件的人们首先看到构建中止的消息而不是别人。