在上一篇博文http://blog.csdn.net/u010487568/article/details/50471592中对C++的模版相关点做了总结,主要包括对模版和泛型编程的理解,对模版的定义与实例化相关内容,本文针对模版更高阶的内容进行一个总结,包括模版编译、模版多功能性、模版特化等内容。
编译器在看到模版定义时不产生代码,这与看到类定义不产生代码一样,只会在看到用模版产生类并使用了类的对象、或调用了函数模版是,编译器才产生特定的模版实例,这一过程也称为实例化。
普通函数和类的成员函数的定义一般可以放在源文件中,而函数声明和类定义需要放在头文件中。由于模版需要进行实例化,编译器必须访问定义模版的源码。标准C++为编译模版代码定义了两种模型,分别为“包含编译模型”和“分别编译模型”。要编译定义的类模版和函数模板,必须了解所使用的编译器是如何处理实例化的。
编译器必须看到所有模版的定义,一般在声明函数模版或类模版的头文件中显示添加一条include语句,将相关定义的源文件包含:
//header file, util.h
#ifndef UTIL_H
#define UITL_H
//declarations
template <typename T> int cmp(const T &, const T &);
...
//include the implements source file
#include "util.cpp"
#endif
上述代码中能够保持头文件和源文件的分离,但必须在头文件最后加上include语句包含相关源文件,保证编译器在编译使用模版的代码时能够看到两种文件。
上述包含结构本质上可以当做是模版的声明和定义都存在于头文件中,不区分声明和定义,合二为一。对于C++的STL,都是在头文件中将模版直接定义的。
编译器在分别编译模型中会跟踪相关模版定义,但必须让编译器知道要记住给定的模版定义,这是通过export关键字完成的。export关键字不必出现在模版声明中,只需在模版定义的template关键字前使用即可。
//函数模版的声明
template<typename T> T sum(T t1, T t2);
//函数模版的定义
export template<typename T> T sum(T t1, T t2){
return t1 + t2;
}
类模版在头文件中不必使用export,否则该头文件只能被程序中的一个源文件使用。只需在类的实现文件中使用export:
//header file
template<typename T> class Queue{
...//declaration
};
//source file
export template<typename T> class Queue;
#include "Queue.h"
//Queue member definitions
...
模版可以作为类、结构或者其他模版的成员,可以将用于常规类的特性全用于模板类(如基类,组件类,类型形参)。
在使用类模版的名字的时候,必须指定模版形参。例外的是:在类模版的作用域内部,可以使用类模版的非限定名。
template<typename T>
class Queue{
public:
Queue(){}
Queue(const Queue &){...}
...
void push(const T &);
...
private:
QueueItem<T> * head;
...
};
template <typename T>
void Queue<T>::push(const T & x)
{
...
}
从上述可以看出,在类的内部,构造函数可以直接使用Queue,本质上编译器推断的是Queue<T>
,这仅仅对于同名来说符合,但对于类的模版成员必须明确指出QueueItem<T>
。
在STL的完全设计实现中,在类或者模版类中使用模版作为成员这项特性是必不可少的。这里可以是模版类或者模版函数,下面示例就是使用模版构造函数:
template<typename T>
class A
{
public:
template<typename I> A(I beg, I end);
A(initializer_list<int>);
A(int);
...
private:
template<typename V> class B;
};
//类外部定义成员模版函数
tempalte<typename T> template<typename I>
A<T>::A(I beg, I end)
{
...
}
上述定义中,构造函数提供的两种不同特定类型的都可以在底层使用模版构造函数实现,调用int构造函数时可以调用初始化列表的构造函数,进而又可以调用模版构造函数传入迭代器来实现。
除此之外,上述定义中使用了类模版B作为成员,也就是完全可以将类模版作为普通类来进行使用。
模版可以包含类型参数和非类型参数,这些参数本身也可以是模版。在STL的实现中使用的也非常广泛。
template< template<typename T>class B>
class A{
B<int> s1;
B<double> s2;
...
};
上述定义中的模版类A的模版参数是一个模版类,参数为B。因此实例化模版类A时必须传入一个模版类作为实参。
A<C> obj; //实例化模版类A
//实例化时传入的模版实参C必须有如下的定义才能匹配
template<typename T>
class C{...};
上述模版类C实例化A后,产生的obj对象,将会有两个数据成员,分别是:C<int> s1
和 C<double> s2
。
模版类可以有友元,分为三类:
非模版友元:模板类中常规声明的友元
普通的常规函数,声明为友元即可,可以携带模版参数也可不携带:
template<typename T>
class HF
{
public:
friend void method1(int);
...
};
method1将是所有实例化的模版类HF的友元函数,为访问模版类的成员,可以使用全局对象、全局指针,或者创建自己的模版类对象,也可以访问静态数据成员。
约束模版友元:友元的类型取决于模板类被实例化时的类型
友元本身为模版时,就可以与模版实例化同步。模版类可以只授予对特定实例的访问权,模版类的参数与友元的模版参数相同,或者指定特定类型的友元。
template<typename T> void method1(const T &);
template<typenamt T> class TplCls;
template<typename V>
class HF
{
friend void method1<V>(const V &);
friend class<string> TplCls;
};
非约束模版友元:友元的所有具体化都是类的每一个具体化的友元
通过在模版类的内部声明新的模版,就能创建非约束的友元,每个实例化都是每个类实例化的友元,友元模版类型参数与模板类的类型参数是不同的:
template<typename T>
class HF
{
public:
template<typename V, typename U> friend void method3(V&, U&);
template<typename I> friend class FF;
...
};
上述定义中,method3与FF与模板类使用不同的模版形参,FF和method3的任意实例均可以访问HF的任意实例的私有成员。这种声明在HF与其友元method3和FF的每个实例之间建立了一对多的映射,也就是对HF 的每个实例,method3和FF的每个实例都是其友元。
最后给出Queue和QueueItem的模版友元声明:
template<typename T> class Queue;
template<typename Type>
class QueueItem{
friend class Queue<Type>;
};
模版特化或者成为具体化是指:泛化的模版类型参数可以指定一个特殊的类型,同时在模版参数列表中去掉相应的模版形参,这是为了针对特定类型的类进行特殊处理,这种特性在STL中的traits技法中进行了巧妙的利用。分为偏特化和全特化。
特化的版本对于用户来说是透明的,编译器会自动优先匹配特化版本,与使用从通用模版实例化得到的版本没有区别。模版特化版本必须让所有使用的源码可见,因此需要与模版泛型版本一起放在头文件中。
将所有模版形参都用特定类型替换,移除所有模版形参。
tempalte<typename T1, typename T2> class Pair{
public:
T1 first();
...
};
//全特化版本
template<> class Pair<const char *, string>{
public:
string first();
...
};
string Pair<const char *, string>::first()
{
...
}
在类的外部定义全特化版本的成员时,不能添加template<>
标记。
另外,可以仅仅特化类的部分成员,这样类的实例化时使用通用定义,当调用特化版本的函数时,如果类型与特化类型匹配,则会调用特化版本的成员:
template<> string Pair<const char *, const char *>::first() //特化版本的成员在类外部单独定义
{
...
}
偏特化又称为部分特化,是指仅仅针对部分模版形参指定特殊类型,其他模版形参保持泛化特性。
//部分特化版本
template<typename T1> class Pair<T1, char *> {...};
Pair<int, string> a;
Pair<int, char *> b;
偏特化版本中,在模板类名称后的尖括号内T1作为占位符,实参char *
作为T2的特化类型。对于部分特化,对象a和b虽然都匹配到通用的版本,但是对象b存在偏特化版本,因此会优先匹配,对象a只会匹配通用版本。