C++类模板实例化

C++类模板实例化

1. 简介

该文章主要总结C++类模板实例化的方法。类模板的实例化包含两种:

  1. 隐式实例化(Implicit instantiation)
  2. 显示实例化(Explicit instantiation)

2. 类模板实例化

类模板本身不是类型、对象或任何其他实体。仅包含模板定义的源文件不会生成任何代码。为了出现任何代码,必须实例化模板:必须提供模板参数,以便编译器可以生成实际的类(或函数,来自函数模板)。
类模板必须实例化才能作为一个类来声明和定义类对象,类模板实例化成为模板类,同一个类模板不同的实例之间相互独立。

2.1 隐式实例化(Implicit instantiation)

C++代码中发生类模板隐式实例化的条件:

  1. 当代码使用类模板定义对象时,需要在上下文中引用完全定义类型。(例如,当构造此类型的对象时,而不是在构造指向此类型的指针时。 )
  2. 当类型的完整性影响代码时,并且该特定类型尚未显式实例化时,就会发生隐式实例化。

此外针对类模板成员适用以下准则:

  1. 除非该成员在程序中使用,否则它不会被实例化,也不需要定义。

下面举例进行说明隐式实例化

template<class T> 
struct Z // 模板定义
{
    void f() {}
    void g(); // 不会被定义
}; 
template struct Z<double>; // 显示实例化 Z<double>
Z<int> a; // 隐式实例化 Z<int>
Z<char>* p; // 无任何实例化生成
p->f(); // 隐式实例化 Z<char> and Z<char>::f().
// Z<char>::g() 不会被声明和定义

错误的实例化
对类模板进行实例化时,如果只声明了类模板但未定义,则实例化会产生不完整的类类型。如下面代码所示:

template<class T> 
class X; // 声明但未定义
 
X<char> ch;                // error: incomplete type X<char>

2.2 显示实例化(Explicit instantiation)

显示实例化的方法如下:

  1. template class-key template-name < argument-list >
    显式实例化定义强制实例化它们所引用的类。 它可以出现在模板定义之后的程序中的任何位置,并且对于给定的参数列表,在整个程序中只允许出现一次,不需要诊断。
  2. extern template class-key template-name < argument-list > (since C++11)
    显式实例化声明(外部模板)跳过隐式实例化步骤:否则会导致隐式实例化的代码改为使用其他地方提供的显式实例化定义(如果不存在此类实例化,则会导致链接错误)。这可用于通过在除使用它的源文件之一之外的所有源文件中显式声明模板实例化并在其余文件中显式定义它来减少编译时间。

类、函数、变量和成员模板特化可以从它们的模板中显式实例化。 成员函数、成员类和类模板的静态数据成员可以从它们的成员定义中显式实例化。
显式实例化只能出现在模板的封闭命名空间中,除非它使用了qualified-id

下面举例说明类模板的显示实例化

namespace N 
{
  template<class T> 
  class Y // 模板定义
  { 
    void mf() { } 
  }; 
}
// template class Y<int>; // 错误: 类模板Y在全局空间不可见
using N::Y;
// template class Y<int>; // 错误:在类模板Y定义的区域外实例化
template class N::Y<char*>;      // 正确: 显示实例化
template void N::Y<double>::mf(); // 正确: 显示实例化

注意与显示特化冲突:
如果之前针对同一组模板参数出现了显式特化,则显式实例化无效。

显示实例化注意事项:

  1. 在显式实例化函数模板、变量模板或类模板的成员函数或静态数据成员或成员函数模板时,仅要求声明可见;
  2. 完整的定义必须出现在类模板、类模板的成员类或成员类模板的显式实例化之前,除非具有相同模板参数的显式特化出现在之前;
  3. 如果使用显式实例化定义类模板的函数模板、变量模板、成员函数模板或成员函数或静态数据成员,则模板定义必须存在于同一编译单元中;
  4. 当显式实例化命名类模板特化时,它充当其每个非继承的非模板成员的相同类型(声明或定义)的显式实例化,这些成员以前未在编译单元中显式特化。 如果此显式实例化是定义,则它也是仅针对此时已定义的成员的显式实例化定义;
  5. 显式实例化定义忽略成员访问说明符:参数类型和返回类型可能是私有的。。

2.3 类模板中的静态成员

如果类模板中有静态成员,则每个模板类分别占用一个静态成员。
以下是类模板中的静态成员正确的初始化方法。

template <typename T>
struct S
{
    static double something_relevant;
};

template <typename T>
double S<T>::something_relevant = 1.5;