30

我正在阅读 Herb Sutter 的文章More Exceptional C++关于前向声明的第 37 项说:

#include当前向声明就足够时,永远不要使用标题。仅在不需要流的完整定义#include时才使用。<iosfwd>

此外,我听到了很多关于仅包含编译单元所需的头文件以减少依赖关系的建议。

我完全理解为什么这应该适用于项目标题,但我不太明白为什么包含不必要的标准标题是不好的。

例如我做这样的事情:

//standard_library.h

#ifndef STANDARD_LIBRARY
#define STANDARD_LIBRARY

#include <iostream>
#include <chrono>
#include <thread>
...
// Everything I need in the project
#endif

并在任何地方包含这个单一的标题,我需要一些东西std

我能想象的问题是:

  1. 不需要在 std 命名空间中的 C 库函数污染命名空间。
  2. 编译时间较慢

但到目前为止,我还没有遇到 1. 的重大问题。几乎所有东西都在 std 命名空间中。我也不完全理解为什么 2. 必然是一个重大问题。标准标题很少更改。据我所知,编译器可以预编译它们。当涉及到模板时,它们仅在我需要它们时才被实例化(编译)。

还有好处:

  1. 少打字
  2. 少读书
  3. 不太清楚我需要哪些标头以及某个功能在哪个标头中

我是一个没有大型项目经验的初学者程序员,我真诚地想弄清楚这一点,所以请怜悯我。

4

7 回答 7

25

除了

  • 命名空间污染
  • 编译时间(虽然可以通过预编译的头文件减少,但它会伤害那些编译大型项目的人,因为他们实际上想要使用它,而不是开发 - 你也想考虑偶尔需要的重建)

您将“更少弄清楚我需要哪些标题以及某个函数在哪个标题中”命名为一个好处。我同意这对于设计良好的库和头文件来说是正确的。

然而,在实践中,我遇到了一些错误(至少在使用 MFC/ATL 时),这些错误可以通过找出正确的包含顺序来解决。另一方面,有一天您想解决一个问题,该问题使您穿越包含的标头 - 现在想象您正在查看大量与您的代码文件无关的标头文件。

我的结论是:如果您以后必须维护一个大型项目,那么通过包含一堆不必要的标题所节省的时间并没有得到回报。您在开始包含任何标题之前投入的时间越多,之后您的安全时间就越多 - 但主要是在没有真正意识到它的情况下。

于 2013-05-30T07:32:14.347 回答
3

哦!我知道一个很好的。

我有一个专有库,用于从内存数据中制作精美的 zip 存档文件。它被设计为多平台,但显然在包括 Windows 在内的每个平台上都没有得到足够好的测试。

它在 Linux 和其他 POSIX 系统上运行良好,但是当我试图在我的项目中采用它时,我偶然发现了这一点:如何在本地抑制 #define?

库和 winbase.h(通过最标准的 windows.h 包含)都有一个 CreateFile 实体。而且,就像在 winbase 中它只是一个宏一样,编译器看不到任何问题,除非您实际上尝试在代码中使用 CreateFile。

所以是的,保持命名空间干净可能是个好主意。

于 2013-05-30T07:27:01.420 回答
3

在您的系统上,它可能不会导致太大的减速,但其他人可能会有不同的体验。

从长远来看,计算机将继续变得更快,编译器将继续变得更有效率。在大多数小型项目中,为头文件节省的时间肯定少于等待编译器所花费的增量时间。

但是(对于不预编译或缓存它们的实现)成本将在所有源文件中成倍增加。这会影响非增量构建的速度。

因此,对于一个在多个来源上使用或分布在不同平台上的库,在公开发布之前每隔一段时间删掉一些东西可能仍然是一个好主意。

于 2013-05-30T07:25:29.687 回答
2

这个建议已经有 10 年的历史了。现在有点过时了。计算机的速度提高了数百倍,存储从 G 变成了 T,大量的内存处于闲置状态。

因此,过去的建议可能会降级。如果你最终进行一些实验并提出自己的意见,你在寻找原因时走上正轨,并且可能会走上更好的轨道。

Herb 的项目比你的问题更笼统。C++(有点遗憾)使用文件(翻译单元)模型进行编译——而不是源代码存储库/数据库。(那些尝试过 IBM 的 Visual Age C++ 的人都知道那是多么的好。;)结果就是你把很多东西打包在一起。包含文件不是一个衬里。

因此,当您需要包含一个标题以对某事物进行单一声明时,您碰巧会拖入许多其他事物。

而那些只是为了编译的其他东西可能会拖入其他东西。以此类推。因此,如果您可以避免包含,它可以节省的不是一行而是数千行。并包含可能不是一个而是几十个文件。一种很好的经济方式。当这些文件中的任何一个发生更改时,也需要重建,无论这些更改可能与您的内容无关。

假设您的标头使用 10 个不同类的指针。并且您包括定义它们的所有 10 个标题,而不仅仅是在使用前加上“类”。这意味着任何可能只使用几个的客户端实际上都会将所有十个作为依赖项拖进来。不经济。在我几年前工作的一个项目中,使用了 gtk++。.cpp 文件只有几百行,但预处理器输出为 800k 或超过百万行。不开玩笑。尽管您为少量冗余付出了代价:今天的东西可能是类,但可能是其他东西(例如 typedef 到模板)。_fwd.h 的想法减轻了这一点,但它实际上只是集中了冗余。在实践中,我们在权衡中寻求某种平衡。

但是所有这些想法都不适用于“稳定”无处不在的东西。在将 std:: 大量使用和自然使用的项目中,您可以看到每个源中都包含许多其他标头。因为它们被使用了。如果某些重构今天删除了最后一个向量,它可能会在明天重新增长。

在这里,选择实际上只是在包容发生的“位置”上,而经济则相反。设置所有内容都使用的“通用”标头可以消除其他文件中的大量噪音。特别是如果系统支持这种情况。在VS中你有

  • 预编译头文件,允许一次性编译通用材料并与其他 TU 共享结果
  • 强制包含,这允许您在项目中而不是在源文件中指定您的公共标头
  • 属性表,您将其包含在项目文件中,而不是手动使用这些设置

有了这样的支持,将许多甚至大多数标准头文件放在该公用文件中,以及一些用于向量和其他公用名称的 using 声明可能是完全可行的。

那些说你拖了很多可能导致冲突的名字的人是对的——但同时他们在实践中是错误的,因为最终有人会包含那个额外的标题,如果存在冲突,它就会翻船。除非在项目中禁止使用 std:: 东西,否则我说将其通用名称用于不同目的只是不好的做法。有人想用他自己的类字符串检查代码,并声称它肯定与 std::string 的前缀不同,我称之为“在我的尸体上”。虽然对于罕见的名字来说,解决事故并不是什么大不了的事。

随着时间的推移,项目甚至项目内部的良好平衡会发生变化。

于 2013-05-30T22:33:43.233 回答
1

There is in principle nothing against it.

The only thing that will happen is that your compile times will increase, unless of course you create a precompiled header of that standard_library.h, in which case the impact will be minimal.

Note that most people prefer to minimize their header dependencies. This mostly applies to your own header files, in which case a small change in an unused, but included header in a source file may trigger an unnecessary recompile of said source file for no reason whatsoever, slowing down incremental builds a lot.

于 2013-05-30T07:20:48.040 回答
1

当标题更改时,受影响的程序会更改。更改的程序需要进行测试。选择性最小化暴露并因此进行测试。

于 2019-02-02T01:44:12.313 回答
0

编译时间较慢

这是主要原因。

即使使用预编译的头文件,编译器也必须做更多的工作才能在项目的每个翻译单元中包含标准库中的每个声明。

如果您有一个包含数百个文件的大型项目,那么编译器将被调用数百次,并且每次调用都必须将整个标准库重新加载到内存中。

编译器将使用更多内存来存储所有声明,并且在进行名称查找时必须检查更大的名称集(尽管编译器中体面的哈希表实现应该意味着不会显着影响查找时间,只会影响内存使用.)

于 2013-05-30T09:45:22.317 回答