为什么不总是包括所有标准标题?

时间:2013-05-30 07:16:54

标签: c++ coding-style

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

  

当前向声明足够时,永远不会#include标题。   完整定义a时,首选#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. 编译时间较慢
  3. 但是我没有遇到过1. sofar的重大问题。几乎所有东西都在std命名空间中。我也不完全理解为什么2.必然是一个重大问题。标准标题很少改变。另据我所知,编译器可以预编译它们。对于模板,只有在我需要它们时才会对它们进行实例化(编译)。

    还有一些好处:

    1. 少输入
    2. 少阅读
    3. 少了解我需要哪些标题以及哪个标题是某个功能
    4. 我是一名没有大型项目经验的初学者,我真的很想弄明白,所以请怜悯我。

7 个答案:

答案 0 :(得分:23)

除此之外

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

你将“我需要哪些标题以及某个函数在哪个标题中”作为一个好处。我同意,对于设计良好的库和标题,这可能是真的。

在实践中,我经历过(至少在MFC / ATL中)一些错误,这些错误可以通过确定包含的正确顺序来解决。另一方面,有一天你想要解决一个让你浏览所包含标题的问题 - 现在想象自己看着大量的头文件与你的代码文件无关。

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

答案 1 :(得分:3)

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

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

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

因此,对于在多个来源上使用或分布在不同平台上的图书馆,在公开发布之前,经常和公开发布之前删除这些内容可能仍然是一个好主意。

答案 2 :(得分:3)

哦!我知道一个好人。

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

它在Linux和其他POSIX系统上运行良好但是当我试图在我的项目中采用它时,我已经发现了这个问题:How to suppress #define locally?

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

所以是的,保持名称空间清洁可能是个好主意。

答案 3 :(得分:1)

原则上没有任何反对意见。

唯一会发生的事情是你的编译时间会增加,除非你创建一个standard_library.h的预编译头,在这种情况下,影响将是最小的。

请注意,大多数人更喜欢最小化其标头依赖项。这主要适用于您自己的头文件,在这种情况下,源文件中未使用但包含的头的小变化可能会无缘无故地触发对所述源文件的不必要的重新编译,从而减慢了增量构建的速度。

答案 4 :(得分:1)

这个建议大约有10年的历史。而且有点过时了。计算机的速度上升了几百倍,存储从G变为T,疯狂的内存量闲置在空闲状态。

所以过去的建议可能会降低。你正在寻找理由,如果你最终进行一些实验并形成自己的观点,你可能会走得更好。

Herb的项目比你的问题更通用。 C ++(有点不幸)使用文件(转换单元)模型进行编译 - 而不是源代码存储库/数据库。 (那些尝试IBM的Visual Age C ++的人知道那是怎么回事。)这样做的结果是你把很多东西打包在一起。包含文件不是一个衬里。

因此,当您需要包含标题以获得某个事物的声明时,您就会发生这种情况 拖入很多其他的东西。

其他只是编译的东西可能会拖延其他东西。等等递归。因此,如果你可以避免包含它,它只能保存数千行。并包含可能不是一个而是几十个文件。一种好的经济方式。当任何这些文件发生变化时,也需要重建,无论这些变化可能与您的内容无关。

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

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

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

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

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

那些说你拖着许多可能引起冲突的名字的人是正确的 - 但同时他们对于练习是错误的,因为最终有人会包括那个额外的标题,如果冲突存在,它将推翻船。除非在项目中禁止使用std :: stuff,否则我认为将其常用名称用于不同目的只是不好的做法。有人想用他自己的类字符串检查代码,并声称它与std :: string的前缀不同,我称之为'over my dead body'。虽然对于罕见的名字来说,解决事故并不是什么大问题。

随着时间的推移,项目甚至项目内部的平衡变化是多少。

答案 5 :(得分:1)

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

答案 6 :(得分:0)

  

编译时间较慢

这是主要原因。

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

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

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