显示实例化和显示具体化(模版特例化)

来源:互联网 发布:淘宝满减活动怎么设置 编辑:程序博客网 时间:2024/05/20 05:07
显示实例化
当模板被使用时才会进行实例化,如果相同的实例可能出现在多个对象文件中。当两个或多个独立编译的源文件使用了相同的模板,并提供了相同的模板参数时,每个文件中就会有该模板的一个实例。


在大系统中,在多个文件中实例化相同模板的额外开销可能非常严重。在新标准中,我们可以通过显示实例化来避免这种开销。一个显示实例化有如下形式:


extern  template declaration  //实例化声明

template declaration               //实例化定义
declaration 是一个类或函数声明,其中所有模板参数已被替换为模板实参。例如,
//实例化声明与定义
extern template class Blob<string>; //声明
template int compare (const int&,const int&);  //
定义


当编译器遇到 extern 模板声明时,它不会在本文件中生成实例化代码。将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明(定义)。对于一个给定的实例化版本,可能有多个extern声明,但必须只有一个定义。


由于编译器在使用一个模板时自动对其实例化,因此extern声明必须出现在任何使用此实例化版本的代码之前:

//Application.cpp
//这些模板类型必须在程序其他位置进行实例化

extern template class Blob<string>;
extern template int compare(const int&,const int&);
Blob<string>sa1,sa2;           //实例化会出现在其他位置
//Blob<int>及其接受initializer_list的构造函数在本文件中实例化
Blob<int>a1={0,1,2,3,4,5,6,7,8,9};
Blob<int>a2(a1);             //拷贝构造函数在本文件中实例化
int i=compare(a1[0],a2[0]);  //实例化出现在其他位置

文件Application.o将包含Blob<int>的实例及其接受initializer_list参数的构造函数的拷贝函数的实例。而compare<int>函数和Blob<string>类将不在本文件中进行实例化。这些模版的定义必须出现在程序的其他文件中:
//templateBuild.cpp
//实例化文件必须为每个在其他文件中声明为extern的类型和函数提供一个(非extern)的定义

template int compare(const int&,const int&);
template class Blob<string>;//实例化类模板的所有成员


当编译器遇到一个实例化定义时,它为其生成代码。因此,文件templateBuild.o将会包含compare的int实例化版本的定义和Blob<string>类的定义。当我们编译此应用程序时,必须将templateBuild.o和Application.o链接到一起。


注意:对每个实例化声明,在程序中某个位置必须有其显示的实例化定义。

实例化定义会实例化所有成员


一个类模板的实例化定义会实例化该模板的所有成员,包括内联的成员函数。当编译器遇到一个实例化定义时,它不了解程序使用哪些成员函数。因此,与处理类模板的普通实例化不同,编译器会实例化该类的所有成员。即使我们不使用某个成员,它也会被实例化。因此,我们用来显示实例化一个类模板的类型,必须能用于模板的所有成员。

模版特例化


编写单一模板,使之对任何可能的模板实参都是最合适的,都能实例化,这并不总是能办到。在某些情况下,通用模板的定义对特定类型是不适合的:通用定义可能编译失败或做的不正确。其他时候,我们可以利用某些特定知识来编写高效的代码,而不是从通用模板实例化。当我们不能使用模板版本时,可以定义类或函数模板的一个特例化版本。


我们的compare函数是一个很好的例子,它展示了函数模板的通用定义不适合一个特定类型(即字符指针)的情况。我们希望compare通过调用strcmp比较两个字符指针而非比较指针值。实际上,我们已经重载了compare函数来处理字符串字面常量:

//第一个版本:可以比较任意两个类型
template<typename T>int compare(const T&,const T&);
//第二个版本处理字符串字面常量
template<size_t N,size_t M>
int compare(const char (&)[N],const char (&)[M]);


但是,只有当我们传递 compare一个字符串字面值常量或者一个数组时,编译器才会调用接受两个非类型模版参数的版本。如果我们传递给它字符指针,就会调用第一个版本:

const char *p1="hi",*p2="mom";
compare(p1,p2);
compare("hi","mom");


我们无法将一个指针转换为一个数组引用,因此当参数是p1和p2时,第二个版本的compare是不可行的。

为了处理字符指针(而不是数组),可以为第一个版本的compare定义一个模版特例化版本。一个特例化版本就是模板的一个独立的定义,在其中一个或多个模板参数被指定为特定的类型。
定义函数模板特例化
//compare的特殊版本,处理字符数组的指针
template<>
int compare(const char* const &p1,const char* const&p2)
{
   return strcmp(p1,p2);
}


理解此特例化版本的困难之处是函数参数类型。当我们定义一个特例化版本时,函数参数类型必须与定义一个先前声明的模板中对应的类型匹配。本例中我们特例化:


template<typename T>int compare(const T&,const T&);


其中函数参数为一个特例化版本,其中T为const char*。我们的函数要求一个指向此类型const版本的引用。一个指针类型的const版本是一个常量指针而不是指向const 类型的指针。我们需要在特例化版本中使用的类型是const char* const &,即一个指向const char的const指针的引用。


注意:一个特例化版本本质上是一个实例,而非函数名的一个重载版本。特例化不影响函数的匹配。模板及其特例化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面,然后是这些模板的特例化版本。