简单地回答这个问题,C++ 是一种比市场上可用的其他语言复杂得多的语言。它有一个传统的包含模型,可以多次解析代码,并且它的模板库没有针对编译速度进行优化。
语法和 ADL
让我们通过一个非常简单的例子来看看 C++ 的语法复杂度:
x*y;
虽然您可能会说上面是一个带有乘法的表达式,但在 C++ 中不一定是这种情况。如果 x 是一个类型,那么该语句实际上就是一个指针声明。这意味着 C++ 语法是上下文相关的。
这是另一个例子:
foo<x> a;
同样,您可能认为这是 foo 类型的变量“a”的声明,但它也可以解释为:
(foo < x) > a;
这将使它成为一个比较表达式。
C++ 有一个名为 Argument Dependent Lookup (ADL) 的功能。 ADL 建立了管理编译器如何查找名称的规则。考虑以下示例:
namespace A{
struct Aa{};
void foo(Aa arg);
}
namespace B{
struct Bb{};
void foo(A::Aa arg, Bb arg2);
}
namespace C{
struct Cc{};
void foo(A::Aa arg, B::Bb arg2, C::Cc arg3);
}
foo(A::Aa{}, B::Bb{}, C::Cc{});
ADL 规则规定,考虑到函数调用的所有参数,我们将寻找名称“foo”。在这种情况下,所有名为“foo”的函数都将被视为重载决议。这个过程可能需要一些时间,尤其是在有很多函数重载的情况下。在模板化的上下文中,ADL 规则变得更加复杂。
#include
此命令可能会显着影响编译时间。根据您包含的文件类型,预处理器可能只复制几行代码,也可能复制数千行。
此外,编译器无法优化此命令。如果头文件依赖于宏,您可以复制可以在包含之前修改的不同代码。
这些问题有一些解决方案。您可以使用预编译的标头,它是编译器对标头中所解析内容的内部表示。然而,如果没有用户的努力,这是无法完成的,因为预编译的头文件假定头文件不依赖于宏。
模块功能为此问题提供了语言级别的解决方案。它从 C++20 版本开始提供。
模板
模板的编译速度具有挑战性。每个使用模板的翻译单元都需要包含它们,并且这些模板的定义需要可用。模板的一些实例化最终会成为其他模板的实例化。在某些极端情况下,模板实例化会消耗大量资源。使用模板并且不是为编译速度而设计的库可能会变得很麻烦,正如您在此链接提供的元编程库的比较中看到的那样:http://metaben.ch/。它们的编译速度差异很大。
如果您想了解为什么某些元编程库的编译时间比其他库更好,请查看this video about the Rule of Chiel。
结论
C++ 是一种编译缓慢的语言,因为在最初开发该语言时,编译性能并不是最重要的。结果,C++ 最终得到了一些在运行时可能有效但在编译时不一定有效的特性。
P.S – 我在 Incredibuild 工作,这是一家专门从事 C++ 编译加速的软件开发加速公司,欢迎您try it for free。