C++ 是一种灵活的语言,所以需要一种积极的方法来分析和减少编译时依赖。一种常见的达到这个目的的方法是,将依赖从头文件里转移到源代码文件里。实现这个目的的方法叫做提前声明。
简而言之,这些声明告诉编译器某个函数接受和返回哪些参数,而具体的定义则规定了具体的行为。让我们根据下面两种规则来改进编译时长并减少可移植性问题,并且使用 include-what-you-use 这种自动应用这些规则到你的代码库的工具。
1、提前声明类型以加速编译速度
为了改进编译时间,尽可能地用提前类型声明来代替#include 指令。例如:
#include <iostream>
#include "a.hpp"
#include "b.hpp"
class MyClass
{
A a_;
B* b_;
public:
B& foo(std::string arg);
};
std::ostream& operator<<(std::ostream& out, const MyClass& obj);
编译器如果需要知道它的大小或者接口,那么只需要知道它的类型的定义。而对于B 来说则不是,任何类型的指针或者引用的大小都是一样的。所以这里的 include可以被提前声明替代。
编译器也不需要知道 std::string 和 std::ostream 的类型,因为它们是模板(可能带有其它未知的默认的模板类型参数),因此我们不能提前声明它们。幸运的是,头文件 为 std::ostream 提供了提前声明。即使std::ostream 的定义是需要的, 仅仅提供了它的定义,而没有输入流或者是 std::cin,std::cout 等等。
改进的文件仅需要以下 include 指令和提前声明:
#include <iosfwd>
#include "a.hpp"
class B;
</code></pre>
这可以让生成的文件小很多。
原则:尽可能使用类型提前声明,但是有外部类型的时候需要特别注意。
2、可移植性问题
如果头文件 A 包含了头文件 B,那么你在包含头文件 A 的时候也会获得 B 的定义。这可能会引起一些细微的关于标准库的问题。除了某些例外,并没有定义哪些头文件包含了哪些。
为了改进编译时间,当需要某些特性的声明时,很多编译器实现包含了一些私有头文件而不是超大的公有头文件。所以当你(意外地)依赖某些间接包含的头文件,你的代码可能因为没有包含某些头文件而无法在别的编译器上成功编译。
这也是前一个例子里讲到的:代码没有包含 string 头文件。在我的平台上这段代码依然可以编译,因为 stream 头文件隐式地包含了,但在别的平台则不一定。
原则:将你要用到的头文件包含进来。如果你需要一个声明,包含相应的头文件。即使最后你的代码里有重复的包含指令,多谢头文件guard 指令,这也没什么问题。
3、Include-what-you-use
好消息是你不用自己手动来执行这两条原则。有一个对应的工具,include-what-you-use(IWYU):https://include-what-you-use.org
这是一个 Google 开发的基于 clang 的工具,用于执行这两个原则:尽可能地将包含指令替换为提前声明,如果依赖简介包含则添加相应的包含指令。
从源代码编译或者从网上下载预编译二进制文件后,你可以轻松地将它与 make 或者 CMake 一起使用。如果使用 make,你只需要将 CXX 变量设置为 IWYU,如果是 CMake 则设置好 CMAKE_CXX_INCLUDE_WHAT_YOU_USE 选项。请查看它们的文档获取更多详细信息。你也可以手动执行,它接受跟 clang 一样的参数。
然后它会给你列出将会对每个文件做出的更改。例如根据我们之前举例的文件,它会得出如下结果:
header.hpp should add these lines:
#include // for string
class B;
header.hpp should remove these lines:
– #include “b.hpp” // lines 6-6
The full include-list for header.hpp:
#include // for ostream
#include // for string
#include “a.hpp” // for A
class B;
除此之外还有一个 python 脚本 fix_includes.py。如果你将 IWYU 的输出作为参数给它执行,它会将这些更改立即执行。
原则:偶尔将 IWYU 运行于你的代码。它会改进编译时间和解决可移植性问题。
4、30倍的超快编译加速是什么样的
提前声明只能提供有限的改进。现在越来越多的公司面临着在高峰时段增加计算能力的需求和更快地响应市场的压力。现在,我们可以在高峰时期进行繁重的编译任务,并且加速你的软件开发时间而不需要改变代码或者增添额外硬件。
通过将任务分布在网络中的本地机器或者是虚拟机上,并且无缝地运行,Incredibuild 的创新解决方案可以为耗时的任务进行加速,例如编译、测试和其它。
欢迎点击了解 Incredibuild 加速 C/C++ 构建编译的解决方案,并获取试用 License!