Hi!请登陆

构建吞吐量系列之模板元编程基础

2021-1-16 32 1/16

模板的特化和实例化

模板元编程逐渐变得流行起来,我们可以在越来越多的代码库中看到它的身影。

但是,使用模板元编程带来的一个坏处是:它需要耗费更多的时间来完成编译。

通过研究大型代码工程中的构建优化,我们发现:在大型代码库中,超过一百万次模板特化和模板实例化是十分常见的,这也让我们有机会对构建过程展开比较有效的优化。

在这篇文章中,我将会介绍模板特化和模板实例化之间的区别,以及它们分别如何在MSVC编译器中处理的。我会在另外一篇文章中,介绍如何在大量的模板特化和实例化中寻找性能瓶颈。

在我们开始之前,让我们先熟悉一下在模板元编程中被广泛使用的一些术语。

> 主模板

– 部分特化

> 模板特化

– 显式特化

> 模板实例化

– 隐式模板实例化

– 显式模板实例化

还是使用一个例子来讲述上面这些术语吧:

模板特化和模板实例化通常可以互相替换使用。但是,在评估工程构建时的性能时,它们之间的区别显得十分重要。

下面我们来看一个例子:

在上面的例子中,MSVC编译器会进行如下的处理:

你可以看到,模板的特化会比模板实例化更早地进行,而且,特化的开销也比较小。

当你特化一个函数模板(例如上述代码中的sort_vector )的时候,编译器仅仅是处理模板的声明,而不会处理其定义。

编译器会创建一个内部的特化表达式并将表达式添加到一个映射表中。如果后面有相同的特化再次发生,则编译器会从映射表中找到之前的条目,并进行重用,这样可以避免重复的处理。特化的定义会延迟到特化被实例化的时候才会进行处理。

类似的,当你特化一个模板类时,它的定义也不会处理。模板类特化的实例化处理起来会更加复杂一点。在默认情况下,当特化本身被实例化时,模板类成员的特化并不会被实例化(上面例子中的Vector::clear),它们只有当被真正使用的时候才会完成实例化(上面例子中的Vector::sort),而MSVC编译器会尽可能地延迟实例化。

你可能会想知道,如果我在test中使用sort_vector,会发生什么呢?

在这种情况下,MSVC编译器会对它的处理顺序做出如下的改变:

> 当使用限定的::sort_vector被使用时,它会禁止ADL(argument dependent lookup)操作。

> 当使用非限定的sort_vector被使用时,ADL会计算v对应的关联集合并强制对Vector进行实例化。所以,当处理未决列表时,模板的实例化并不会被延迟。

了解了上面的信息,我们来看看一些比较常见的模式,以及哪些需要模板实例化。

Array<1>场景

当它被当做一个成员的类型使用时,编译器需要实例化这一特化,从而得到它的一些元信息,例如占用内存大小。这就是为什么一个模板特化通常会在头文件中被实例化的最主要原因,并且,通常这一限制也十分难以避免。

Array<2>场景

当使用模板特化作为函数的返回类型时,它通常不需要被实例化(如果没有函数定义的话)。类似的,如果模板特化被当做函数参数的类型时,也会如此。

但是,提供函数的定义,或者调用函数,会强制对返回类型进行实例化。

Array<3>场景

对函数参数传递nullptr,不需要实例化。因为nullptr总是可以被转换为任意指针类型。如果你将nullptr转换为Array<3> *,也是同样的情况。

但是,如果函数的参数是一个指向类的指针,则编译器必须实例化Array<3>以判断转换是否有效。

在下一篇文章中,我们将会使用一些真实世界中的实际例子,来展示减少模板特化和实例化的次数的一些方法。

总结

模板的使用,确实把抽象这一概念提升了一个新的层次,但是,要正确的使用它们,并不容易。

老哥们,还是好好下功夫吧。

最后

Microsoft Visual C++团队的博客是我非常喜欢的博客之一,里面有很多关于Visual C++的知识和最新开发进展。大浪淘沙,如果你对Visual C++这门古老的技术还是那么感兴趣,则可以经常去他们那(或者我这)逛逛。

本文来自:《Build Throughput Series: Template Metaprogramming Fundamentals》

相关推荐