【问题标题】:Include directives in header file? [duplicate]在头文件中包含指令? [复制]
【发布时间】:2012-09-23 18:29:57
【问题描述】:

可能重复:
where should “include” be put in C++

显然,关于是否将 #include 指令放入 C++ 头文件(或者,作为替代方案,仅将 #include 放入 cpp 文件),有两种“思想流派”。有人说没问题,有人说只会出问题。有谁知道这个讨论是否已经得出结论是什么是首选?

【问题讨论】:

  • @chris:我不确定我是否在那里找到了答案。 “作为一项规则,尽可能将您的包含放入 .cpp 文件中,并且仅在不可能的情况下放入 .h 文件中。”我猜总是可以将包含在 cpp 中。
  • 只要你正确地创建了你的包含守卫,你就可以安全地在任何地方包含任何头文件,包括其他头文件。
  • 但并不总是可以省略另一个标题中的包含。

标签: c++ include c-preprocessor


【解决方案1】:

我不知道有任何关于此的思想流派。当需要它们时将它们放在标题中,否则转发声明并将它们放在需要它们的.cpp 文件中。在不需要的地方包含标题没有任何好处。

【讨论】:

  • 我会尝试再次找到参考,但我不确定我是否会成功。
【解决方案2】:

我发现有效的方法是遵循一些简单的规则:

  • 标头应该是自给自足的,即,它们应声明需要为其命名的类,并为所使用的任何定义包含标头。
  • 标头应尽可能减少依赖关系而不违反上一点。

获得第一点是相当容易的:首先从实现它声明的源代码中包含标题。但是,将第二点完全正确并非易事,而且我认为它需要工具支持才能完全正确。但是,一些不必要的依赖项通常并没有那么糟糕。

【讨论】:

  • Re 但是,一些不必要的依赖项通常并没有那么糟糕。 它们当然可能很糟糕。 GNU 的标准库在 4.3 之前包含了很多额外的东西。隐式需要<bar> 的功能但只包含<foo> 的代码可以在标准库的4.3 之前版本中编译得很好,因为标头<foo> 有一个多余的包含<bar>。代码在转换到 4.3 时没有编译,因为该版本摆脱了标准库中的许多多余包含。
  • 不必要的依赖是将不相关的东西聚集到一个头文件中的结果。当您包含标题时,您会在该标题中获得所有内容。该标头中的所有内容都不是您从该标头中实际需要的确切依赖项集。并且该标头包括其他标头,并且还从它们中获得了比它严格需要的更多的东西,依此类推。在这些预处理器语言中解决这个问题的唯一方法是设计巧妙、细粒度的标头。
  • @DavidHammen:我同意你的说法,我认为只包括(并提供)必要的内容很重要。然而,如果没有工具检测不必要的标题,几乎不可能完全正确。不幸的是,创建一个用于检测是否包含正确标题的工具并非易事,请参见例如Google's header checker.
  • @DietmarKühl:我同意所有这些。我的规则:#1:头文件不应该包含另一个头文件,其功能与所讨论的头文件正交。 (不要仅仅因为实现标头的 5 个源文件中的 2 个需要另一个标头而 #include 某些东西。) #2:如果可能的话,使用前向声明而不是标头包含。 #3:包括定义有问题的头代码插入的对象的头文件。 #4:考虑将在其他对象中插入的标头代码移动到某个源文件中。最后一件事:为您的回答 +1。
【解决方案3】:

根据经验,只要需要对它们进行完整定义,就不会在标题中包含标题。大多数时候你在头文件中玩弄类的指针,所以在那里转发声明它们就可以了。

【讨论】:

    【解决方案4】:

    我认为这个问题很久以前就解决了:标题应该是自包含的(即不应该依赖于用户之前包含其他标题 - 这方面已经解决了很长时间,以至于有些甚至没有意识到对此存在争议,但您的 put 仅包含在 .cpp 中似乎暗示了这一点)但很少(即,当声明足以自包含时,不应包含定义)。

    自包含的原因是维护:如果一个标头被修改并且现在依赖于新的东西,您必须跟踪它用于包含新依赖项的所有位置。顺便说一句,确保自包含的标准技巧是在 .cpp 中首先包含为 .cpp 中定义的事物提供声明的标头。

    【讨论】:

      【解决方案5】:

      这些不是思想流派,而是宗教。实际上,这两种方法都有其优点和缺点,并且要使任何一种方法成功,都需要遵循某些实践。但这些方法中只有一种会“扩展到”大型项目。

      在标头中不包含标头的好处是编译速度更快。但是,这种优势并不来自只读取一次的标头,因为即使您在标头中包含标头,智能编译器也可以解决这个问题。速度优势来自于您只包含给定源文件严格必需的那些标头。另一个优点是,如果我们查看源文件,我们可以准确地看到它的依赖关系:头文件的平面列表清楚地向我们提供了这一点。

      但是,这种做法很难维持,尤其是在有很多程序员的大型项目中。当你想使用模块 foo 时,这很不方便,但你不能只使用 #include "foo.h":你需要包含 35 个其他标头。

      最终发生的事情是:程序员不会浪费时间去发现他们只需添加模块foo 所需的确切的、最小的标头集。为了节省时间,他们将转到一些与他们正在处理的源文件类似的示例源文件,然后剪切并粘贴所有#include 指令。然后他们将尝试编译它,如果它没有编译,那么他们将从其他地方剪切并粘贴更多 #include 指令,并重复此操作直到它工作。

      最终结果是,您逐渐失去了编译速度更快的优势,因为您的文件现在包含不必要的标头。此外,#include 指令列表不再显示真正的依赖关系。此外,当您现在进行增量编译时,由于这些错误的依赖关系,您编译的内容超出了必要的范围。

      一旦每个源文件都包含几乎每个标头,您最好有一个包含所有标头的大 everything.h,然后在每个源文件中使用 #include "everything.h"

      因此,这种仅包含特定标头的做法最好留给由少数开发人员精心维护的小型项目,这些开发人员有足够的时间手动维护最小包含依赖项的道德,或者编写工具来寻找不必要的 @ 987654329@ 指令。

      【讨论】:

      • 不包括不必要的头文件的好处不仅是更快的编译。减少依赖是一个非常重要的因素。为什么要耦合不需要耦合的事物?
      • 是的,依赖减少是通过包含标题的标题自动实现的,而不是通过手动包含标题。你让头文件包含头文件,并在所有头文件中都有“包含保护”,然后编译器将始终只包含每次编译时确切依赖项的传递闭包,同时避免两次读取任何头文件。
      • 不,您不能像这样删除依赖项。包含另一个标题的标题,即使它间接包含,也对它有依赖性。包括守卫与它无关。
      • 对不起,我听不懂你在说什么。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-29
      • 1970-01-01
      • 2022-01-20
      • 2020-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多