【发布时间】:2011-06-22 15:46:04
【问题描述】:
它到底想做什么邪恶的魔法!?!
我在听Q&A session with herb sutter,其中一个问题是关于概念的。 Herb 提到它使编译器变慢(而源代码保持不变)并且该部分明显大于模板部分。
为什么要这样做?我在哪里可以找到有关概念的文档?
【问题讨论】:
标签: c++ c++11 c++-concepts
它到底想做什么邪恶的魔法!?!
我在听Q&A session with herb sutter,其中一个问题是关于概念的。 Herb 提到它使编译器变慢(而源代码保持不变)并且该部分明显大于模板部分。
为什么要这样做?我在哪里可以找到有关概念的文档?
【问题讨论】:
标签: c++ c++11 c++-concepts
注意:以下答案(及其回答的问题)与旧 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 函数模板的正确性,因为在concepts-lite 世界中,由开发人员正确指定类型的要求。
这种差异会在编译时间上产生相当大的巨大差异。 2016 年有两篇论文考虑了概念进入 C++17 与否的原因:
“Why I want Concepts, and why I want them sooner rather than later” 和
“Why I want Concepts, but why they should come later rather than sooner.”
甚至都没有考虑性能,因此这是一个很好的指标,表明当时它不是问题。
此外,当前的概念设计可能会带来一些性能优势。根据the rule of Chiel 的说法,编译中最慢的事情是 SFINAE,因为它至少需要尝试(通常)实例化大量类型,然后才放弃它们。概念(取决于它们的实现方式)可能不需要实例化任何模板,这实际上可能最终成为性能优势。
【讨论】:
您可能会在ConceptsGCC website 上找到有用的资源。那是他们正在构建的编译器(从 GCC 分叉),以查看这个概念(请原谅双关语)是否可行。
我认为开销来自于必须对各种语言结构执行彻底、普遍和递归的有效性检查,并且鉴于您可以指定一组非常丰富的约束,检查这些约束可能会变得非常昂贵。
有点像异常规范的噩梦版!
【讨论】: