Fork me on GitHub

模板参数与成员模版

模板参数

模板参数与作用域

模板参数遵循普通的作用域规则。一个模板参数名的可用范围是在其声明之后,至模板声明或定义结束之前。与任何其他名字一样,模板参数会隐藏外层作用域中声明的相同名字。但是,与大多数其他上下文不同,在模板内不能重用模板参数名:

1
2
3
4
5
6
typedef double A;
template <typename A,typename B>
void f(A a, B b){
A tmp = a; //tmp的类型为模板参数A的类型,而非double
double B; //错误:重声明模板参数B
}

由于参数名不能重用,所以一个模板参数名在一个特定模板参数列表中只能出现一次:

1
2
//错误:非法重用模板参数名V
template <typename V, typename V> //...

模板声明

模版声明必须包含模板参数:

1
2
3
//声明但不定义compare和Blob
template <typename T> int compare(const T&,const T&);
template <typename T> class Blob;

与函数参数相同,声明中的模板参数的名字不必与定义中相同。

建议: 一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置,出现于任何使用这些模板的代码之前。

使用类的类型成员

例如,假定T是一个类型参数的名字,当编译器遇到如下形式的语句时:

1
T::size_type * p;

它需要知道我们是正在定义一个名为p的变量还是将一个名为size_type的static数据成员与名为p的变量相乘。

默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。因此,如果我们希望使用一个模板参数的类型成员,就必须显式告诉编译器该名字是一个类型。我们通过使用关键字typename来实现这一点:

1
2
3
4
5
template <typename T>
//typename T::value_type 使用关键字通过编译器该名字T::value_type表示类型
typename T::value_type top (const T &c) {
//...
}
note:当我们希望通知编译器一个名字表示类型时,必须使用关键字typename,而不能使用class。

默认模板实参

就像我们能为函数参数提供默认实参一样,我们也可以提供默认模板实参(default template argument)。在新标准中,我们可以为函数和类模板提供默认实参。

例如,我们重写compare,默认使用标准库的less函数对象模板:

1
2
3
4
5
6
template <typename T,typename F = less<T>>
int compare(const T &v1,const T &v2, F f = F()){
if(f(v1,v2)) return -1;
if(f(v2,v1)) return 1;
return 0;
}

模板默认实参与类模板

无论何时使用一个类模板,我们都必须在模板名之后接上尖括号。尖括号指出类必须从一个模板实例化而来。特别是,如果一个类模板为其所有模版参数都提供了默认实参,且我们希望使用这些默认实参,就必须在模板名之后跟一个空尖括号对:

1
2
3
4
5
6
7
8
9
10
11
12
template <class T = int>
class Numbers{ //T默认为int
public:
Numbers(T v=0):val(v) {}
//...
private:
T val;
};
Numbers<long double> num1;
Numbers<> num2; //空<>表示我们希望使用默认类型

习题示例:

编写函数,接受一个容器的引用,打印容器中的元素,两种方式,一种使用容器的size_type和size成员来控制打印元素的循环;另一种使用begin和end返回迭代器来控制循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template<typename Container>
void print1(const Container &container){
//typename Container::size_type 表明size_type是一个类型
for(typename Container::size_type i = 0;i<container.size();++i){
cout<<container[i]<<" ";
}
cout<<endl;
}
template<typename Container>
void print2(const Container &container){
for(auto iter = container.begin();iter!=container.end();++iter){
cout<<*iter<<" ";
}
cout<<endl;
}
vector<int> ivec = {1,2,3,4};
print1(ivec);
list<string> slist = {"wang","xin","ri"};
print2(slist);

成员模板

一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板(member template)。成员模板不能是虚函数。

普通(非模板)类的成员模板

例如,我们定义一个类,我们的类将包含一个重载的函数调用运算符,它接受一个指针并对此指针执行delete。由于希望delete使用于任何类型,所以我们将调用运算符定义为一个模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
using namespace std;
class DebugDelete{
public:
DebugDelete(ostream &s = cerr):os(s){}
//与任何函数模板相同,T的类型由编译器推断
template<typename T>
void operator()(T *p) const { //成员模板,重载函数调用运算符
os<<"deleting unique_ptr"<<endl;
delete p;
}
private:
ostream &os;
};
int main()
{
double *p = new double(10);
cout<<*p<<endl;
DebugDelete d;
d(p); //调用DebugDelete::operator() (double*),释放p
int *ip = new int;
//在一个临时DebugDelete对象上调用operator()(int*)
DebugDelete()(ip);
return 0;
}

类模板的成员模板

对于类模板,我们也可以为其定义成员模板。再此情况下,类和成员各自有自己的、独立的模板参数。

与类模板的普通函数成员不同,成员模板是函数模板。当我们在类模板外定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表。类模板的参数列表在前,后跟成员自己的模板参数列表:

1
2
3
4
//类模板外定义成员模板
template <typename T> //类的类型参数
template <typename It> //构造函数的类型参数
Blob<T>::Blob(It b,It e):data(make_shared<vector<T>>(b,e)) {}

控制实例化

当模板被使用时才会进行实例化这一特性意味着,相同的实例可能出现在多个对象文件中。当两个或多个独立编译的源文件使用了相同的模板,并提供了相同的模板参数时,每个文件中就都会有该模板的一个示例。

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

1
2
extern template declaration; //实例化声明
template declaration; //实例化定义

declaration是一个类或函数声明,其中所有模板参数已被替换为模板实参。例如,

1
2
3
//实例化声明与定义
extern template class Blob<string>; //声明
template int compare(const int&,const int&); //定义

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

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

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

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

note: 在一个类模板的实例化定义中,所有类型必须能用于模板的所有成员函数。

-------------本文结束感谢您的阅读-------------

本文地址:http://www.wangxinri.cn/2017/12/01/模板参数与成员模版/
转载请注明出处,谢谢!

梦想夹带眼泪,咸咸的汗水!