Sma*_*acL 34 c++ templates compile-time
我正在将我的C++应用程序的一部分从使用旧的C类型数组更改为模板化的C++容器类.有关详情,请参阅此问题.虽然解决方案运行良好,但我对模板化代码所做的每一个小改动都会导致进行大量的重新编译,从而大大减慢构建时间.有没有办法从标题中取出模板代码并返回到cpp文件,这样小的实现更改不会导致重大的重建?
Jos*_*ley 26
几种方法:
void*; 所有的复杂性都void*存在于.cpp文件中的向量中.Scott Meyers在Effective C++中给出了一个更详细的例子(第42版"明智地使用私有继承").Unc*_*ens 19
我认为一般规则适用.尝试减少代码部分之间的耦合.将太大的模板标题分解为一起使用的较小的函数组,因此整个事物不必包含在每个源文件中.
另外,尝试快速将标头置于稳定状态,也许可以针对较小的测试程序对其进行测试,因此当集成到更大的程序中时,它们不需要更改(太多).
(与任何优化一样,在处理模板时优化编译器的速度可能不太值得,而不是找到一个"算法"优化,从而首先大幅减少工作量.)
小智 10
使用模板作为解决问题的技术可能会导致编译速度减慢。一个经典的例子是 C 语言中的 std::sort 与 qsort 函数。该函数的 C++ 版本需要更长的时间来编译,因为它需要在每个翻译单元中进行解析,而且几乎每次使用该函数都会创建一个不同的实例该模板的(假设闭包类型通常作为排序谓词提供)。
\n尽管这些速度下降是可以预料的,但有一些规则可以帮助您编写高效的模板。下面描述其中的四个。
\n下面介绍的 Chiel 规则描述了哪些 C++ 结构对于编译器来说是最困难的。如果可能,最好避免这些构造以减少编译时间。
\n以下 C++ 功能/结构按编译时间降序排序:
\nBoost.TMP在设计和开发时采用了基于上述规则的优化。尽可能避免使用顶级构造来快速编译模板。
\n下面是一些示例,说明如何利用上面列出的规则。
\n让我们看一下 std::conditional。其声明是:
\ntemplate< bool B, typename T, typename F >\nstruct conditional;\nRun Code Online (Sandbox Code Playgroud)\n每当我们更改赋予该模板的三个参数中的任何一个时,编译器都必须创建它的新实例。例如,想象以下类型:
\nstruct first{};\nstruct second{};\nRun Code Online (Sandbox Code Playgroud)\n现在,以下所有内容都将以不同类型的实例化结束:
\nusing type1 = conditional<true, first, second>;\nusing type2 = conditional<true, second, first>;\nstd::is_same_v<type1, type2>; // it\xe2\x80\x99s false\n\nusing type3 = conditional<false, first, second>;\nusing type4 = conditional<false, second, first>;\nstd::is_same_v<type1, type2>; // it\xe2\x80\x99s false\nRun Code Online (Sandbox Code Playgroud)\n我们可以通过将条件的实现更改为以下来减少实例化的数量:
\ntemplate <bool>\nstruct conditional{\n template <typename T, typename F>\n using type = T;\n};\n\ntemplate <>\nstruct conditional<false>{\n template <typename T, typename F>\n using type = F;\n};\nRun Code Online (Sandbox Code Playgroud)\n在这种情况下,编译器将为所有可能的参数仅创建两个 \xe2\x80\x9cconditional\xe2\x80\x9d 类型的实例。有关此示例的更多详细信息,请查看Odin Holmes 关于 Kvasir 库的演讲。
\n每当您怀疑模板的实例将被经常使用时,显式实例化它\xe2\x80\x99 是一个好主意。通常,std::string是 的显式实例化std::basic_string<char>。
Kvasir-MPL 专门针对长类型列表的算法来加速它们。您可以在此处查看相关示例。在此头文件中,排序算法是手动专门针对 255 种类型的列表。手动专门化可以加快长列表的编译速度。
\n首先,为了完整起见,我将介绍直接的解决方案:仅在必要时使用模板化代码,并将其基于非模板代码(在其自己的源文件中实现).
但是,我怀疑真正的问题是你使用泛型编程,因为你会使用典型的OO编程并最终得到一个膨胀的类.
我们来举个例子:
// "bigArray/bigArray.hpp"
template <class T, class Allocator>
class BigArray
{
public:
size_t size() const;
T& operator[](size_t index);
T const& operator[](size_t index) const;
T& at(size_t index);
T const& at(size_t index);
private:
// impl
};
Run Code Online (Sandbox Code Playgroud)
这让你震惊吗?可能不是.毕竟它看起来很简约.问题是,事实并非如此.at可以在不失一般性的情况下考虑这些方法:
// "bigArray/at.hpp"
template <class Container>
typename Container::reference_type at(Container& container,
typename Container::size_type index)
{
if (index >= container.size()) throw std::out_of_range();
return container[index];
}
template <class Container>
typename Container::const_reference_type at(Container const& container,
typename Container::size_type index)
{
if (index >= container.size()) throw std::out_of_range();
return container[index];
}
Run Code Online (Sandbox Code Playgroud)
好的,这稍微改变了调用:
// From
myArray.at(i).method();
// To
at(myArray,i).method();
Run Code Online (Sandbox Code Playgroud)
但是,感谢Koenig的查找,只要将它们放在同一个名称空间中,就可以将它们称为不合格,所以这只是习惯问题.
这个例子是人为的,但总的来说就是这样.请注意,由于它的通用性,at.hpp从来没有必须包含bigArray.hpp并且仍然会产生紧密的代码,就好像它是一个成员方法一样,只是我们可以根据需要在其他容器上调用它.
现在,如果用户不使用它,BigArray则不需要包括at.hpp...因此,如果更改该文件中的代码,则减少其依赖性并且不受影响:例如,更改std::out_of_range调用功能文件名和行号,容器的地址,大小和我们尝试访问的索引.
另一个(不那么明显)的优点是,如果BigArray违反了完整性约束,那么at显然是不合理的,因为它不会弄乱类的内部,从而减少了嫌疑人的数量.
这是许多作者推荐的,例如C++编码标准中的 Herb Sutters :
第44项:更喜欢写非会员非友好职能
并且已被广泛用于Boost ......但你必须改变你的编码习惯!
当然,您只需要包含您所依赖的内容,应该有静态C++代码分析器报告包含但未使用的头文件,这可以帮助解决这个问题.