多次包含相同的头文件会增加编译时间吗?
例如,假设我的项目中的每个文件都使用<iostream>
<string>
<vector>
和<algorithm>
. 如果我在源代码中包含很多文件,那会增加编译时间吗?
我一直认为保护标头具有避免双重定义的重要目的,但作为副产品也消除了双重代码。
实际上,我认识的某个人提出了一些想法来删除这种多重夹杂物。但是,我认为它们完全违背了 c++ 中的良好设计实践。但仍然想知道他提出这些更改的原因可能是什么?
多次包含相同的头文件会增加编译时间吗?
例如,假设我的项目中的每个文件都使用<iostream>
<string>
<vector>
和<algorithm>
. 如果我在源代码中包含很多文件,那会增加编译时间吗?
我一直认为保护标头具有避免双重定义的重要目的,但作为副产品也消除了双重代码。
实际上,我认识的某个人提出了一些想法来删除这种多重夹杂物。但是,我认为它们完全违背了 c++ 中的良好设计实践。但仍然想知道他提出这些更改的原因可能是什么?
这些答案中的大多数都是错误的……对于现代编译器,假设标头使用通常的“包含保护”习语,多次包含同一个文件的开销为零。
例如,GCC 预处理器具有识别包含保护习语的特殊代码。它甚至不会打开第二个和后续#include
指令的头文件(不要介意阅读它)。
我不确定其他编译器,但如果它们中的大多数没有实现相同的优化,我会感到非常惊讶。
除了预编译头文件之外的另一种技术是编译器防火墙习语,解释如下:
每次#include <something.h>
在源文件中发生时,都必须沿着包含路径找到“something.h”并读取。但是有#ifndef _SOMETHING_H_
检查,所以这样something.h的内容不会被编译。因此有一些开销,但它确实很小。
一些编译器,尤其是 Microsoft 的编译器,有一个#pragma once
指令,您可以使用该指令在包含文件后自动跳过它。这消除了任何性能损失。
这可能是一个问题。正如其他人所说,大多数现代编译器都会智能地处理这种情况,并且只会在退化的情况下重新打开文件。然而,大多数并不是全部,主要的例外之一是微软,很多人都必须支持它。最可靠的解决方案(如果这在您的环境中确实是一个问题)是使用 Lakos 约定,将包含保护放在
#include
标头中以及标头中。当然,这意味着生成保护名称的标准约定。(对于外部包含,将它们包装在您自己的标题中,这尊重您的本地约定。)或者,您可以同时使用守卫和#pragma once
. 守卫将始终有效,并且大多数编译器将避免额外的打开,并且#pragma once
通常会避免与 Microsoft 的额外打开。(#pragma once
在复杂的网络情况下无法可靠地实现,但只要您的所有文件都在本地驱动器上,它就非常可靠。)
最好的方法是使用预编译的头文件。我不知道您使用的是哪个编译器,但它们中的大多数都有此功能。我建议你参考你的编译器手册来了解如何实现这一点。
它基本上收集所有头文件并将其编译成一个目标文件,然后链接器可以使用该目标文件。这大大加快了编译速度。
小缺点:
您需要有 1 个“uberheader”,它包含在每个编译单元 (.cpp) 中。
在那个 uberheader 中,只包含来自库的静态标题,而不是你自己的。然后编译器不需要经常重新编译它。
它有帮助,尤其是。当使用诸如 boost 或 glm、eigen 等纯头文件库时。
高温高压
是的,多次包含相同的标头意味着需要在预处理器保护启动并防止多个定义之前打开文件。Mozilla 源代码使用以下技巧来防止这种情况:
Foo.h
#ifndef FOO_H
#define FOO_H
// whatever
#endif /* FOO_H */
在所有需要包含 foo.h 的文件中
#ifndef FOO_H
#include "foo.h"
#endif
这可以防止 foo.h 必须多次打开。当然,这取决于每个人都遵循他们的预处理器守卫的特定命名约定。
您不能使用标准标头来执行此操作,因为它们的预处理器防护没有通用的命名约定。
编辑:
再次阅读您的问题后,我认为您是在询问不同源文件中包含相同的标头。我上面所说的对此无济于事。每个头文件仍然必须在每个翻译单元中打开并至少包含一次。我知道防止这种情况的唯一方法是使用预编译的标头,正如@scorcher24在他的回答中提到的那样。但是我会远离这个解决方案,因为没有跨编译器生成预编译头文件的标准方法,除非编译时间绝对令人望而却步。