包含小工具





5.00/5 (7投票s)
包含保护、Pragma Once、前向声明以及其他在处理包含时可能有所帮助的技巧。
包含保护、Pragma Once、前向声明以及其他在处理包含时可能有所帮助的技巧。
在 C++ 中,我们可以使用文件包含做什么?我们需要在 *每个* 文件中 *始终* 包含项目中的 *所有* 其他头文件(以及第三方库)吗?当然,必须有一些规则来妥善管理这个问题。
本文讨论的问题当然并非新鲜事。每个 C++ 程序员都应该知道如何正确使用 #include
。但不知何故,我仍然看到大量代码混乱不堪,编译时间过长……更糟糕的是(在大多数其他情况下也是如此),即使你花了一些时间尝试遵循一些良好的 #include
策略,一段时间后混乱仍然会从文件中滋生。当然,我也对这类错误负有责任。
问题是什么?
为什么最小化头文件和 include
语句的数量如此重要?
这是一张通用图
你看到了答案吗?当然,你的程序可能有更复杂的结构,所以再添加 100 个文件并将它们随机连接起来。
C++ 编译器处理头文件的过程
- 读取所有头文件(打开文件,读取其内容,如果发生错误则列出错误)
- 将头文件的内容“泵入”到一个翻译单元
- 解析并获得头文件内代码的逻辑结构
- 需要运行旧的 C 宏,这甚至可能改变文件的最终结构
- 模板实例化
- 大量涉及
string
的操作
如果存在太多冗余,编译器需要花费更长的时间工作。
有什么指导方针吗?
- 到处使用前向声明!
- 尽量在任何可能的地方使用它们。这将减少包含文件的数量。请注意,在需要某个类型(在函数中、作为类成员)时,包含文件对于编译器来说可能并非如此关键——它只需要知道其名称,而不需要完整的定义。
- 头文件顺序
- 文件 *myHeader.h*(包含一些类)应该首先包含(或者紧随通用预编译头文件之后),并且是自包含的。这意味着当我们在项目的其他地方使用 *myHeader.h* 时,我们不需要知道它有哪些额外的包含依赖项。
- 速度
- 现代编译器在优化头文件访问方面做得相当不错。但来自我们这边的额外帮助也会很有益。
- 预编译头文件可以节省生命和时间。尽可能多地放入系统和第三方库的头文件。不幸的是,当需要跨平台解决方案并且包含过多时,事情可能会变得棘手。在此阅读更多:gamesfromwithin
- Pragma Once、Include Guards 和 Redundant Include Guards:在选择最佳组合方面没有明确的赢家。在 Visual Studio 中,PragmaOnce 似乎很棒,但它不是一个标准化的解决方案。例如,GCC 通常在标准 Include Guards 方面表现更好。
- 工具
正如我们所见,通过使用指针或引用作为成员或参数声明,我们可以大幅减少包含的数量。总的来说,我们应该只包含编译文件所需的最小文件集。甚至有可能将这个数字减少到零。
理想情况下
#ifndef _HEADER_A_INCLUDED_H
#define _HEADER_A_INCLUDED_H
class A
{
};
#endif // _HEADER_A_INCLUDED_H
以及在源文件中
#include <stdafx.h> // precompiled if needed
#include "A.h"
#include "..." // all others
// implementation here
有希望吗?
头文件可能非常成问题,而且这绝对不是 C++ 语言的一大特性。如果包含过多,编译时间会越来越长。而且很难控制。但还有其他选择吗?其他语言如何处理类似的问题?
很难将 Java 和 C# 的编译与 C++ 相提并论:C++ 生成针对特定架构优化的本地二进制代码。托管语言编译成某种中间语言,这比本地代码容易得多。值得一提的是,托管语言使用模块(而不是包含),这些模块几乎是编译代码的最终版本。这样,编译器就不必一遍又一遍地解析模块。它只需要获取所需和已编译的数据和元数据。
因此,看来 C++ 的主要问题在于缺乏 **模块**。这个想法可以减少翻译单元创建时间,最小化冗余。我以前已经提到过:通过 clang 实现 C++ 的模块(这里 或 这里)。另一方面,C++ 编译非常复杂,因此引入它并不容易,更重要的是,**标准化** **模块**概念。
链接
- 链接到有趣(且更普遍)的问题:为什么 C++ 编译要花这么长时间
- John Lakos 的《Large Scale C++ Software Design》——我在上一篇关于封装的文章中提到了它。书中详细讨论了 C++ 代码的物理结构。建议所有 C++ 程序员阅读。
- even-more-experiments-with-includes - @Games From Within
- RedundantIncludeGuards - 一种简单的技术,在包含某个内容之前,只需检查其包含保护是否已定义。在旧编译器中,这可以提高性能,但在现代解决方案中,使用它的好处并不那么明显。
待续...
在不久的将来,我将尝试在这里发布一些关于编译时间和 #include
技巧的基准测试。