单一来源项目结构的缺点是什么?

9
我是您当前公司的新员工,正在处理由我的直接团队领导编写的项目。公司通常不使用C++,但我的同事用C/C++编写了有效的代码。只有我们两个人知道如何使用C++(我和我的领导),因此没有第三方意见可以参与其中。
在我对项目有足够的了解后,我意识到整个结构非常特殊。
实际上,它由一个单一的编译单元组成,其中makefile仅将main.hpp列为唯一源文件。
然后,该头文件包含项目中包含的所有源文件,因此看起来像一个非常大的列表。
#include "foo.cpp"
#include "bar.cpp"

尝试理解其背后的逻辑后,我意识到对于该项目来说这种方式确实有效。因为每个单元可以在不访问任何其他单元的情况下运行,所以我曾经问他为什么要这样做,但却遭到了抵触的反应:
“嗯,它能运行,不是吗?如果你认为自己有更好的方式,那就按照自己的想法去做吧。”
因为我真的很难理清这种结构,所以现在我正在采用“通常”的结构来编写实现,同时只对整个项目进行必要的更改,以演示我会如何设计它。
我认为存在许多缺点,从混合连接器和编译器工作的项目结构开始就无法良好服务;再到可能导致冗余或模糊结果的优化,更不用提一个干净的项目构建需要大约30分钟,我认为这也可能是由于结构造成的。但我缺乏知识来列举实际而非假设性的问题。
既然他的论点“按我的方式工作,不是吗?”是正确的,我希望能够向他解释为什么这是一个坏主意,而不是显得过于挑剔。
因此,这种项目结构实际上可能会引起哪些问题呢?或者我是过度反应了,这种结构完全没问题吗?

3
在我看来 - 保释。与这样的人一起工作将是噩梦般的事情。 - UKMonkey
6
仅有一个编译单元意味着每次进行微小更改时都需要重新编译所有内容。具有良好分离结构的多个编译单元有助于大幅减少编译时间。 - AMA
2
我知道有些项目将所有源代码放入同一个单元中,以启用额外的编译优化并挤出一些额外的性能。然而,这只是为发布做准备的一部分。开发仍然需要使用适当的多单元结构。 - AMA
1
哦,就是一个大文件。这个文件有多大?现在我明白你的问题是要找到使用多个编译单元的好理由,只是想了解情况。 - Griffin
3
我认为最好的论点是编译时间(正如答案中提到的这里),其次是易于阅读,这通常被程序员低估。 - Griffin
显示剩余7条评论
2个回答

5
主要缺点是修改代码的任何部分都需要从头重新编译整个程序。如果编译需要一分钟,这可能并不重要,但如果需要30分钟,那就会很痛苦;它会破坏更改->编译->测试的工作流程。
另一个潜在的缺点是整个编译过程必须适合内存。只有当该内存超过开发人员工作站上的免费内存时,才会出现问题。
总之:单一的大源码文件方法不能很好地适应大型项目的规模。
现在,公平起见,说一下优势
实际上,单个翻译单元比分开的更容易优化。这是因为某些优化(尤其是内联扩展)无法跨翻译单位进行,因为它们依赖于当前编译的翻译单元中不存在的定义。
自从流行编译器的稳定版本中提供了链接时优化以来,这种优化优势已得到缓解。只要您能够并愿意使用现代编译器,并启用链接时优化(可能不是默认启用)
PS。给单个源文件以 .hpp 扩展名非常不常规。

有趣的一点关于内存,我还没有考虑过。关于唯一的源文件扩展名是 .hpp,我发现这个 文件看起来更像一个头文件而不是源文件,这更加不寻常。当我修改 makefile 时,我才意识到这一点。 - dhein
@dhein 嗯,这只是这种方法的一个结果。单个源文件只是编译过程中的工具,而不应该包含程序逻辑。 - eerorika
2
实际上,如果你有一台多核机器,你可以使用make -jN或ninja并行编译多个编译单元。即使没有分布式构建,Visual Studio也会并行化至少独立的项目,因此我不会将这种优势限制在分布式编译农场中。 - PaulR
@PaulR 我曾经想象过编译器在编译单个编译单元时也可以跨多个核心共享负载。也许我错了。 - eerorika
对于Visual C++,我认为你可能是正确的,如果我记得编译期间我的CPU负载正确的话(但这可能只适用于链接时优化),但我不认为gcc和clang使用多个线程(除了某些形式的LTO)。另请参阅https://softwareengineering.stackexchange.com/questions/322494/do-compilers-utilize-multithreading-for-faster-compile-times。 - PaulR
“.hpp” 文件扩展名在这种模式中有些传统/常见,虽然不是最广泛或可扩展的解决方案,但在少数 C++ 程序员中相对流行,据我所知。至少在我的计算机科学学位的前几年就接触到了它——我并不自称是 C++ 人口统计学专家。一些开发人员发现这样更容易推理,而对此的回答很像这个答案的关键——在某种程度上,它可以为某些项目的推理或组织带来优势,但不具备良好的可扩展性。 - mtraceur

3

首先我想提到的是单一编译单元项目的优点:

  • 大幅缩短编译时间。这实际上是切换到SCU的主要原因之一。当您有一个常规项目,其中包含n个翻译单元时,每添加一个新的翻译单元,编译时间将呈线性增长。而使用SCU,则会呈对数增长,向大型项目添加新单元几乎不会影响编译时间。
  • 减少编译内存。包括磁盘和RAM。 "大"翻译单元显然会占用比仅包含项目部分的每个“小”翻译单元更多的内存,但它们的累积大小将大大超过“大”翻译单元的大小。
  • 一些优化好处。显然,您会自动获得“所有内容都内联”。
  • 不再担心“从头开始编译”。这非常重要,因为这是CI服务器执行的操作。

现在来看缺点:

  • 开发人员必须保持严格的头文件组织纪律。包括头文件守卫、一致的#include指令排序、直接需要当前文件的头文件强制包含、正确的转发、一致的命名规范等等。问题在于,目前没有工具可以帮助开发人员处理这些问题,即使是在头文件组织中出现轻微的违规行为也可能导致构建日志混乱失败。
  • 项目中的总文件数可能会增加。详情请参见this answer
  • 不再有"正在编译"的借口来玩木剑游戏了。

P.S.

  • 在您的情况下,SCU组织有点“软煮”。我的意思是该项目仍然具有不适当的标头翻译单元。通常,这种情况发生在将旧项目转换为SCU时。
  • 如果使用SCU构建项目需要大约30分钟的时间,我感觉问题不在于项目组织(可能是杀毒软件、没有SSD、递归模板膨胀),否则如果没有SCU,则需要几个小时。
  • 一些数字:编译时间从大约14分钟降至约20秒,可执行文件大小缩小3倍(根据我从现有项目转换为SCU的经验得出的结果)
  • 实际应用案例:CppCon 2014: Nicolas Fleury "C++ in Huge AAA Games", Chromium Jumbo / Unity builds ("is can save hours for a full build")
  • 我可能有点夸张,但对我来说,“多个翻译单元”的整个概念(以及静态库)似乎应该留在过去。

“这样的项目结构”是指哪个?您提到了时间缩短,但解释说它是对数而不是线性,我认为这并没有缩短。回答很好,但我希望您能更清楚地说明您所指的是什么 :) - dhein
谢谢,输入很好,非常有趣。但我不确定你是在谈论SCU vs SCU的一般情况还是针对我描述的特定情况?或者他所做的实际上是SCU的正确概念吗?如果不是,您可以详细说明可能存在的错误实现SCU的问题吗?虽然这目前很有趣,但它并不能帮助我找到反对现有结构(而不是SCU总体)的原因。 - dhein
@dhein 我是在谈论SCU的一般情况。但是我提到了你所拥有的那种项目(即编译包含大量.cpp文件的单个文件)被称为“半成品”,因为这是一种“走捷径”的方法。维护如此庞大的列表似乎是相当繁琐的。适当的SCU(在我看来)意味着将所有东西都写成仅头文件库(即编译只包括一个“应用程序头文件库”头文件和入口点的单个文件,没有任何“巨大的列表”)。 - user7860670
3
“整个“多重翻译单元”的概念(以及静态库)应该留在过去” - 这是一个非常强烈的声明。它不应该从所提供的论证/案例中得出。是的,单一构建具有某些优点。我认为这种概括是有害的。” - AMA
2
一个值得考虑的好例子是 sqlite 项目。他们将代码作为一个大的 .c 文件发布,但如果你浏览它正在开发和版本控制的代码,它会被适当地组织成单独的 .c 文件。 - mtraceur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接