生成的包含卫兵:一次替代实用主义
介绍
在C ++中,没有什么可以阻止程序员多次包含头文件。 这可能会导致定义重复,这是一个错误。 由于很难确保只将头文件包含一次,因此常见的策略是仅对第一个包含计数进行计数。 可以使用“包含保护”来完成,这是一小段预处理器逻辑,如下所示:
它是如何工作的?
在第一个include上, HEADER_HAS_BEEN_INCLUDED
未定义,因此我们定义foo
。 在随后的包含中,已定义HEADER_HAS_BEEN_INCLUDED
,因此我们仅跳过内容。
例如,如果我们有此C ++文件:
然后它将扩展为:
在预处理程序完成后,我们将得到以下结果:
这是惯用的方法,但是有一些限制:
- 需要三行样板代码
- 第1行和第2行的变量名称必须完全匹配
- 相同的变量名不能在多个文件中使用
- 我们必须记住
#endif
,它位于文件#ifndef
的另一端
那一次实用主义呢?
#pragma once
旨在克服这些问题。 它是C ++编译器的非标准功能,但得到了广泛支持。 这个概念很简单:任何包含#pragma once
文件实际上只会被包含一次,即使程序员多次包含它。
#pragma once
使用#pragma once
,我们的示例变为:
看起来不错吧? 可悲的是, #pragma once
带来了许多问题。
根本原因是#pragma once
涉及其中一些代码的生活,而不是它的内容 。 如果您可以通过多个路径访问同一文件的两个副本,那么它将被包含两次。 而且,如果您有两条路径看起来不同但实际上相同,那么编译器可能不会发现这一点。 最重要的是,它不是标准的,因此编译器实现不必尊重其语义。
可能的解决方法
#pragma once
的问题#pragma once
源于它在文件的位置而不是其内容上起作用的事实。 如果我们只是使用内容呢? (当然,记录每个标题的所有内容会很慢,但是我们可以通过记录内容的哈希值来进行优化)。
该过程将是:
1.包含头文件时,对其进行哈希处理
2.如果以前已经看到过哈希,则忽略包含
3.否则,正常包含标题
这将是一个可靠的解决方案,因为它根本不关心文件所在的路径,而只关心文件的内容。
实施解决方法
将新命令添加到C ++标准将花费大量时间,但是幸运的是,我们可以使用脚本和预处理器来实现此逻辑。
基本思想是这样的:
因此,例如此标头:
SHA-256哈希值为:
因此,生成的标头可能是:
尽管单个文件的转换很简单( Python脚本 ),但我们仍然需要管理转换过程。 我们需要确保:
- 为每个文件运行转换
- 新文件将自动转换
- 删除文件的转换会自动删除
- 仅在文件更改后才重新运行转换
- 奖励:转换可以安全地放入共享的网络缓存中
使用Buck build ,我们可以轻松地将此逻辑编码到项目的构建脚本中。
让我们从单个文件的构建规则开始,然后进行概括:
Buck中的genrule
很像Make中的目标。 我们定义输入文件,输出文件名和要执行的命令。 该目标使用我们的Python脚本来生成包含保护,并在add.hpp
上运行它。 与Make不同,Buck将在输入哈希中隔离并缓存该进程。
现在我们只有一个文件,我们可以将流程推广到n
文件。 为此,我们制作了一个Python函数,该genrule
为给定的文件创建了一个genrule
:
要获取头文件集,我们运行一个glob表达式。 例如:
并将所有内容整合在一起:
现在,我们的头文件可以一次编写而无需包含保护或#pragma once
:
Buck中的此设置非常适合与以下对象一起使用:
- 头文件中的零样板
- Buck会自动检查新的头文件,因此构建始终是最新的
- Buck将删除过时的头文件
- 因为它了解目标图,所以Buck将并行生成标头
- Buck将缓存生成的头,以便仅在需要时才计算
- 我们不再依赖人类的准确性(包括防护措施)或非标准功能(
#pragma once
)
既然你在这里...
我们创建了Buckaroo ,以便更轻松地集成C ++库。 如果您想尝试一下,最好的起点是文档 。 您可以浏览Buckaroo.pm上的现有软件包,或在愿望清单上请求更多。
From: https://hackernoon.com/generated-include-guards-an-alternative-to-pragma-once-31cc3dee6ce