几个原因
头文件
每个编译单元都需要数百甚至数千个头文件(1)加载和(2)编译。通常必须为每个编译单元重新编译它们中的每一个,因为预处理器确保编译头的结果可能在每个编译单元之间变化。(可以在一个编译单元中定义宏,该编译单元改变标题的内容)。
这可能是主要原因,因为它需要为每个编译单元编译大量代码,此外,每个头文件必须多次编译(每个编译单元包含它一次)。
链接
编译完成后,所有目标文件必须链接在一起。这基本上是一个单一的过程,不能很好地并行化,并且必须处理整个项目。
解析
解析时语法极其复杂,在很大程度上取决于上下文,并且很难消除歧义。这需要很多时间。
模板
在C#中,List无论你在程序中有多少个List实例,它都是唯一被编译的类型。在C ++中,vector是一个完全独立的类型vector,每个都必须单独编译。
除此之外,模板构成了编译器必须解释的完整图灵完整的“子语言”,这可能变得非常复杂。即使是相对简单的模板元编程代码也可以定义递归模板,这些模板可以创建数十个模板实例。模板也可能导致极其复杂的类型,名称冗长,为链接器添加了大量额外的工作。(它必须比较许多符号名称,如果这些名称可以增长到数千个字符,那可能会变得相当昂贵)。
当然,它们会加剧头文件的问题,因为模板通常必须在头文件中定义,这意味着必须为每个编译单元解析和编译更多的代码。在普通的C代码中,标头通常只包含前向声明,但实际代码很少。在C ++中,几乎所有代码都驻留在头文件中并不罕见。
优化
C ++允许一些非常戏剧性的优化。C#或Java不允许完全删除类(它们必须用于反射目的),但即使是简单的C ++模板元程序也可以轻松生成数十个或数百个类,所有这些类都在优化中被内联并消除相。
此外,编译器必须完全优化C ++程序。AC#程序可以依赖JIT编译器在加载时执行额外的优化,C ++没有得到任何这样的“第二次机会”。编译器生成的内容是最优化的。
机
C ++被编译为机器代码,这可能比字节码Java或.NET使用更复杂(特别是在x86的情况下)。(这完全是出于完整性而被提及,因为它在评论等中提到过。在实践中,这一步骤不太可能占用总编译时间的一小部分)。
结论
大多数这些因素都由C代码共享,实际上编译效率相当高。解析步骤在C ++中要复杂得多,并且可以占用更多的时间,但主要的攻击者可能是模板。它们很有用,并且使C ++成为一种更强大的语言,但它们也会在编译速度方面付出代价。