它到底想做什么邪恶的魔法!?!
我正在听一个关于草本萨特的问答环节,其中一个问题是关于概念的。Herb 提到它使编译器变慢(而源代码保持不变)并且该部分明显大于模板部分。
为什么这样做?我在哪里可以找到有关概念的文档?
它到底想做什么邪恶的魔法!?!
我正在听一个关于草本萨特的问答环节,其中一个问题是关于概念的。Herb 提到它使编译器变慢(而源代码保持不变)并且该部分明显大于模板部分。
为什么这样做?我在哪里可以找到有关概念的文档?
注意:以下答案(及其回答的问题)与旧的 C++0x 版本的概念有关,与添加到 C++20 的功能版本几乎没有关系。
首先,Herb 并没有说概念本身会使编译速度变慢。他说,概念化 C++ 标准库使任何使用 C++ 标准库的代码编译速度变慢。
其原因归结为几件事。
1:约束模板需要编译时间。
当您声明这样的类时:
template<typename T> class Foo {...};
编译器只是解析 Foo 并且做的很少。即使使用两阶段查找,编译器也不会在类 Foo 的编译中做很多事情。当然,它会存储它以备后用,但初始传递速度相对较快。
当您使用概念约束模板时:
template<ConceptName C> class Foo {...};
编译器必须做一些事情。它必须预先检查该类型的每次使用是否C
符合概念ConceptName
。这是编译器会推迟到实例化时间的额外工作。
您进行的概念检查越多,用于验证类型是否与概念匹配的编译时间就越多。
2:标准C++库使用了很多概念。
查看迭代器概念的数量:输入、输出、转发、双向、顺序、连续。委员会正在考虑将它们分解成更多。许多算法对于不同的迭代器概念会有多个版本。
这不包括范围概念(除了输出之外的每种迭代器概念都有一个)、std::string 的字符概念以及各种其他类型的东西。所有这些都必须编译和检查。
真正需要使其快速的概念是模块。编译器能够生成包含一系列预检查符号的模块文件,然后直接加载该文件而无需经过标准编译过程。直接从解析到符号创建。
请记住:对于您 #include 的每个 .cpp 文件,编译器必须读取该文件并对其进行编译。即使每次执行此操作时文件都是相同的,它仍然必须尽职尽责地读取文件并处理它。如果我们谈论的是概念化std::vector
,它必须对模板进行所有概念检查。它仍然必须完成编译时所做的所有标准符号查找。等等。
想象一下,如果编译器不必这样做。想象一下,如果它可以直接从磁盘加载一堆符号和定义。根本不需要编译;只需引入符号和定义供其他代码使用。
它就像预编译的头文件只会更好。预编译的头文件被限制为每个 .cpp 文件只有一个,而您可以使用任意数量的模块。
遗憾的是,模块在这个过程中很早就从 C++0x 中删除了。如果没有模块,用概念约束标准库的编译速度总是比不受约束的版本慢。
请注意,Herb 误解了模块的用途(并不难,因为该功能的大多数初始概念都是他所说的:跨平台 DLL 等)。它们的核心基本目的是帮助编译时间,而不是使跨平台 DLL 工作。模块本身也不是跨平台的。
由于这个问题已经很老了(从 2011 年开始),并且在撰写本文时(2020 年)最近发布了一些概念,我想澄清一些事情,只是为了不误导人们或阻止他们使用概念。
过去考虑的概念和现在发布的概念是完全不同的存在。C++20 中发布的概念也被称为“概念精简版”,因为与概念的初始设计相比,它们包含的功能有所减少。那么,从概念中拿走了什么?
主要区别在于概念的主要设计不仅用于检查模板使用的正确性,还用于检查该模板定义的正确性。例如,假设您有一个类型为 的模板Animal
,它需要具有成员函数make_sound
。你可以想象一个这样的约束函数模板:
template <typename Animal>
requires requires(Animal& x){
x.make_sound();
}
int animal_tricks(Animal& x) {
x.make_sound();
x.do_trick();
}
现在,在概念的初始设计中,函数模板的定义animal_tricks
将是不正确的,因为我们使用的是do_trick
成员函数,它不是所需表达式的一部分。使用 C++20 精简版概念,这个概念的定义很好。编译器不会检查animal_tricks
函数模板的正确性,因为在概念精简版世界中,由开发人员正确指定类型的要求。这种差异会对编译时间产生很大的影响。2016 年,有两篇论文讨论了概念是否进入 C++17 的原因:
“为什么我想要概念,以及为什么我要早点而不是晚点”</a>和
“为什么我想要概念,但为什么它们应该迟而不是早。”</a> 甚至都没有考虑性能,所以这是一个很好的指标,表明当时它不是问题。
此外,当前的概念设计可能会带来一些性能优势。根据Chiel 的规则,编译中最慢的事情是 SFINAE,因为它至少需要尝试(通常)实例化大量类型,然后才放弃它们。概念(取决于它们的实现方式)可能不需要实例化任何模板,这实际上可能最终成为性能优势。
您可能会在ConceptsGCC 网站上找到有用的资源。那是他们正在构建的编译器(从 GCC 分叉),以查看这个概念(请原谅双关语)是否可行。
我想成本来自必须对各种语言结构执行彻底、普遍和递归的有效性检查,并且鉴于您可以指定一组非常丰富的约束,检查这些可能会变得非常昂贵。
有点像异常规范的噩梦版本!