模板的局限性:
假设有如下模板函数:
template <class T>
void f(T a, T b)
{…}
通常,代码假定可执行哪些操作。例如,下面的代码假定定义了赋值,但如果T为数组,这种假设将不成立:
a = b;
同样,下面的语句假设定义了<,但如果T为结构,该假设便不成立:
if(a > b)
另外,为数组名定义了运算符>,但由于数组名为地址,因此它比较的是数组的地址,而这可能不是您希望的。下面的语句假定为类型T定义了乘法运算符,但如果T为数组、指针或结构,这种假设便不成立:
T c = a*b;
总之,编写的模板函数很可能无法处理某些类型。另一方面,有时候通用化是有意义的,但C++语法不允许这样做。例如,将两个包含位置坐标的结构相加是有意义的,虽然没有为结构定义运算符+。一种解决方案是,C++允许您重载运算符+,以便能够将其用于特定的结构或类。这样使用运算符+的模板便可以处理重载了运算符+的结构。另一种解决方案是,为特定类型提供具体化的模板定义,下面就来介绍这种解决方案。
显式具体化:
假设定义了如下结构:
struct job
{
char name[40];
double salary;
int floor;
};
{
char name[40];
double salary;
int floor;
};
另外,假设希望能够交换两个这种结构的内容。原来的模板使用下面的代码来完成交换:
temp = a;
a = b;
b = temp;
a = b;
b = temp;
由于C++允许将一个结构赋给另一个结构,因此即使T是一个job结构,上述代码也适用。然而,假设只想交换salary和floor成员,而不交换name成员,则需要使用不同的代码,但Swap()的参数保持不变(两个job结构的引用),因此无法使用模板重载来提供其他的代码。
然而,可以提供一个具体化函数定义——称为显式具体化(explicit specialization),其中包含所需的代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。
具体化机制随着C++的演变而不断变化。下面介绍C++标准定义的形式。
1.第三代具体化(ISO/ANSI C++标准)
试验其他具体化方法后,C++98标准选择了下面的方法。
- 对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及它们的重载版本。
- 显式具体化的原型和定义应以template<>开头,并通过名称来指出类型。
- 具体化优先于常规模板,而非模板函数优先于具体化和常规模板。
下面是用于交换job结构的非模板函数、模板函数和具体化的原型:
//non template function prototype(非模板函数)
void Swap(job &, job &);
//template prototype(模板函数)
template <typename T>
void Swap(job &, job &);
//explicit specialization for the job type(具体化)
template <> void Swap<job>(job &, job &);
void Swap(job &, job &);
//template prototype(模板函数)
template <typename T>
void Swap(job &, job &);
//explicit specialization for the job type(具体化)
template <> void Swap<job>(job &, job &);
正如前面指出的,如果有多个员原型,则编译器在选择原型时,非模板版本优先于显式具体化和模板版本,而显式具体化优先于使用模板生成的版本。例如,在下面的代码中,第一次调用Swap()时使用通用版本,而第二次调用使用基于job类型的显式具体化版本。
…
template <class T> //templatevoid Swap(T &, T &);
//explicit specialization for the job type
template<> void Swap<job>(job &, job &);
int main()
{
double u, v;
…
Swap(u, v); //use template
job a, b;
…
Swap(a, b); //use void Swap<job>(job &, job &)
}
Swap<job>中的<job>是可选的,因为函数的参数类型表明,这是job的一个具体化。因此,该原型也可以这样编写:
template<> void Swap(job &, job &);//simpler form
下面看一看显式具体化的工作方式。
2.显示具体化示例
- <span style=“font-size:14px;”>#include<bits/stdc++.h>
- using namespace std;
- struct job
- {
- char name[40];
- double salary;
- int floor;
- };
- template<typename T>
- void Swap(T &a, T &b)
- {
- T temp;
- temp = a;
- a = b;
- b = temp;
- }
- template<> void Swap<job>(job &j1, job &j2)
- {
- double t1;
- int t2;
- t1 = j1.salary;
- j1.salary = j2.salary;
- j2.salary = t1;
- t2 = j1.floor;
- j1.floor = j2.floor;
- j2.floor = t2;
- }
- void Show(job &j)
- {
- cout << j.name << ”: $” << j.salary
- << ” on floor ” << j.floor << endl;
- }
- int main()
- {
- cout.precision(2);
- cout.setf(ios::fixed, ios::floatfield);
- int i = 10, j = 20;
- cout << ”i, j = ” << i << “, ” << j << “.\n”;
- cout << ”Using compiler-generated int swapper:\n”;
- Swap(i, j);
- cout << ”Now i, j = ” << i << “, ” << j << “.\n\n”;
- job sue = {”Susan Yaffee”, 73000.60, 7};
- job sidney = {”Sidney Taffee”, 78060.72, 9};
- cout << ”Before job swapping:\n”;
- Show(sue);
- Show(sidney);
- Swap(sue, sidney);
- cout << ”After job swapping:\n”;
- Show(sue);
- Show(sidney);
- return 0;
- }
- </span>
#include<bits/stdc++.h>
using namespace std;
struct job
{
char name[40];
double salary;
int floor;
};
template<typename T>
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
template<> void Swap<job>(job &j1, job &j2)
{
double t1;
int t2;
t1 = j1.salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.floor;
j1.floor = j2.floor;
j2.floor = t2;
}
void Show(job &j)
{
cout << j.name << ": $" << j.salary
<< " on floor " << j.floor << endl;
}
int main()
{
cout.precision(2);
cout.setf(ios::fixed, ios::floatfield);
int i = 10, j = 20;
cout << "i, j = " << i << ", " << j << ".\n";
cout << "Using compiler-generated int swapper:\n";
Swap(i, j);
cout << "Now i, j = " << i << ", " << j << ".\n\n";
job sue = {"Susan Yaffee", 73000.60, 7};
job sidney = {"Sidney Taffee", 78060.72, 9};
cout << "Before job swapping:\n";
Show(sue);
Show(sidney);
Swap(sue, sidney);
cout << "After job swapping:\n";
Show(sue);
Show(sidney);
return 0;
}
实例化和具体化:
为进一步了解模板,必须理解术语实例化和具体化。记住,在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例(instantiation)。例如,上面代码中,函数调用Swap(i, j)导致编译器生成Swap()的一个实例,该实例使用int类型。模板并非函数定义,但使用int的模板实例是函数定义。这种实例化方式被称为隐式实例化(implict instantiation),因为编译器之所以知道需要进行定义,是由于程序调用Swap()函数时提供了int参数。
最初,编译器只能通过隐式实例化, 来使用模板生成函数定义。但现在C++还允许显式实例化(explici instantiation)。这意味着可以直接命令编译器创建特定的实例,如Swap<int>()。其语法是,声明所需的种类——用<>符号指示类型,并在声明前加上关键字template:
template void Swap<int>(int , int);//explicit instantiation
实现了这种特性的编译器看到上述声明后,将使用Swap()模板生成一个使用int类型的实例。也就是说,该声明的意思是”使用Swap()模板生成int类型的函数定义。“
与显示实例化不同的是,显式具体化使用下面两个等价的声明之一:
template <> void Swap<int>(int &, int &);//explicit specialization
template <> void Swap(int &, int &);//explicit specialization
区别在于,这些声明的意思是”不要使用Swap()模板来生成函数定义,而应使用专门为int类型显式地定义的函数定义。“这些原型必须有自己的函数定义。显式具体化声明在关键字template后包含<>,而显式实例化没有。
警告:试图在同一个文件(或转换单元)中使用同一种类型的显式实例和显式具体化将出错。
还可通过在程序中使用函数来创建显式实例化。例如, 请看下面代码:
- <span style=“font-size:14px;”>#include<bits/stdc++.h>
- using namespace std;
- template <class T>
- T Add(T a, T b)
- {
- return a+b;
- }
- int main()
- {
- int m = 6;
- double x = 10.2;
- cout << Add<double>(x, m) << endl;<span style=“white-space:pre;”> </span>显式实例化
- return 0;
- }</span>
#include<bits/stdc++.h>
using namespace std;
template <class T>
T Add(T a, T b)
{
return a+b;
}
int main()
{
int m = 6;
double x = 10.2;
cout << Add<double>(x, m) << endl; 显式实例化
return 0;
}
☆这里的模板与函数调用Add(x, m)不匹配,因为该模板要求两个函数参数的类型相同。但通过使用Add<double>(x, m),可强制为double类型实例化,并将参数m强制转换为double类型,以便与函数Add<double>(double, double)的第二个参数匹配。
如果对Swap()做类似的处理,结果将如何呢?
int m = 5;
double x = 14.3;
Swap<double>(m, x);
这将为double生成一个显式实例化。不幸的是,这些代码不管用,因为第一个形参的类型为double &,不能只想int变量m。
显式具体化、显式实例化、隐式实例化统称具体化。
</div>
</div>