简介
本文中的内容主要参考C++ Primer Plus(第6版)中文版
一书第14章中的内容。该篇博客主要根据自己使用模板的经验,结合书中的内容,总结类模板的原理和使用方法。
定义类模板
C++类模板为生成通用的类生命提供了一种更好的方法。模板提供参数化类型,即能够将类型名作为参数传递给接收方来建立类或函数。类模板以template <typename Type>
开头,关键字template
告诉编译器将要定义一个模板。
类模板的定义如下代码所示:
template <typename Type>
class Stack
{
private:
enum {MAX = 10}; // constant specific to class
Type items[MAX]; // holds stack items
int top; // index for top stack item
public:
Stack();
bool isempty();
bool isfull();
bool push(const Type & item); // add item to stack
bool pop(Type & item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
Stack& operate=(const Stack& st);
};
template <class Type>
Stack<Type>::Stack()
{
top = 0;
}
template <class Type>
bool Stack<Type>::isempty()
{
return top == 0;
}
template <class Type>
bool Stack<Type>::isfull()
{
return top == MAX;
}
template <class Type>
bool Stack<Type>::push(const Type & item)
{
if (top < MAX)
{
items[top++] = item;
return true;
}
else
return false;
}
template <class Type>
Stack<Type> & Stack<Type>::operator=(const Stack<Type> & st)
{
}
上述代码中列出了类模板和成员函数模板,这些模板不是类和成员函数的定义,它们只是C++编译器指令,说明了如何生成类和成员函数定义,因此不能将模板成员函数放在独立的实现文件中,必须与特定的模板实例化请求一起使用,最简单的方法是将所有模板信息放在一个头文件中,并在要使用这些模板的文件中包含该头文件。
注意:原型将赋值运算符函数的返回类型声明为Stack
引用,而实际的模板函数定义将类型定义为Stack<Type>
。前者是后者的缩写,缩写只能够在类中使用,但在类的外面,必须要指定返回类型以及使用作用域解析运算符时,必须使用完整的Stack<Type>
。
使用模板类
仅在程序中包含模板并不能生成模板类,而必须请求实例化。
Stack<int> kernels;
Stack<string> colones;
上述代码中分别定义了两个不同类型的模板类。但需要注意的是与定义模板函数不同的是,定义模板类必须显示地提供所需的类型。因为函数模板可以根据参数的类型来确定要生成哪种函数,如下代码所示:
template <typename T>
void simple(T t) { cout << t << '\n'; }
simple(2); // generate void simple(int)
simple("two") // generate void simple(const char*)
数组模板示例和非类型参数
模板常用作容器类,下面将通过一个例子来深入探讨模板设计和使用的其他几个方面。
template <class T, int n>
class ArrayTP
{
private:
T ar[n];
public:
ArrayTP() {};
explicit ArrayTP(const T & v);
virtual T & operator[](int i);
virtual T operator[](int i) const;
};
template <class T, int n>
ArrayTP<T,n>::ArrayTP(const T & v)
{
for (int i = 0; i < n; i++)
ar[i] = v;
}
template <class T, int n>
T & ArrayTP<T,n>::operator[](int i)
{
if (i < 0 || i >= n)
{
std::cerr << "Error in array limits: " << i
<< " is out of range\n";
std::exit(EXIT_FAILURE);
}
return ar[i];
}
template <class T, int n>
T ArrayTP<T,n>::operator[](int i) const
{
if (i < 0 || i >= n)
{
std::cerr << "Error in array limits: " << i
<< " is out of range\n";
std::exit(EXIT_FAILURE);
}
return ar[i];
}
上述代码中关键字class指出T为类型参数,int指出n的类型为int,这种参数称为非类型(non-type)或表达式(expression)参数。声明形式如下:
ArrayTP<double, 12> eggweights;
注意:表达式参数有一些限制,表达式参数可以是整型、枚举、引用或还真,因此,double m
是不合法的,但double *rm
和double *pm
是合法的。另外模板代码不能修改参数的值,也不能使用参数的地址。另外,实例化模板时,用作表达式参数的值必须是常量表达式。
模板多功能性
- 递归使用模板
注意:在模板语法中,维的顺序与等价的二维数组相反。ArrayTP< ArrayTP<int,5>, 10> twodee; 该定义等价于常规数组声明:int twodee[10][5]
- 使用多个类型参数
模板可以包含多个类型参数,下面代码中就使用了两种类型参数。template <class T1, class T2> class Pair { }
- 默认类型模板参数
类模板的另一新特性是可以为类型参数提供默认值:
虽然可以为类模板类型参数提供默认值,但不能为函数模板参数提供默认值。然后可以为非类型参数提供默认值,这对于类模板和函数模板都是适用的。template <class T1, class T2 = int> class Topo { ... };
模板的具体化
类模板与函数模板很相似,可以有隐式实例化、显示实例化和显示具体化。
-
隐式实例化
目前为止,上述代码都是适用的隐式实例化(implicit instantiation)。即它们声明一个或多个对象,指出所需的类型,而编译器使用通用模板生成具体的类定义:ArrayTP<int, 100> stuff; // implicit instantiation
编译器在需要对象之前,不会生成类的隐式实例化:
ArrayTP<double, 30>* stuff; // a pointer, no object needed yet stuff = new ArrayTP<double, 30>; // noew an object is needed
-
显示实例化
template class ArrayTP<int, 100> ; // explicit instantiation
在这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。
-
显示具体化
template <> class Classname<specialized-type-name> { ... }; // explicit specialized
-
部分具体化
C++还允许部分具体化(partial specialization),即部分限制模板的通用性。例如,部分具体化可以给类型参数之一指定具体的类型:template<typename T1> class Pair<T1, int> { ... };
也可以通过为指针提供特殊版本来部分具体化现有的模板:
template<class T> // general version class Feeb { ... } template<class T*> // pointer partial specialization version class Feeb { ... } Feeb<char> fb1; // use general Feeb template Feeb<char *> fb1; // use Feeb T* specialization
部分具体化特性使得能够设置各种限制。
// generate template template <class T1, class T2, class T3> class Trio { ... }; // specilization with T3 set to T2 template <class T1, class T2, class T3> class Trio<T1, T2, T2> { ... }; // specilization with T3 and T2 set to T1* template <class T1> class Trio<T1, T1*, T1*> { ... };
成员模板
模板可用作结构、类或模板类的成员。如下述代码所示:
template <typename T> class beta { private: template <typename V> // nested template class member class hold { private: V val; public: hold(V v = 0) : val(v) {} void show() const { cout << val << endl; } V Value() const { return val; } }; hold<T> q; // template object hold<int> n; // template object public: beta( T t, int i) : q(t), n(i) {} template<typename U> // template method U blab(U u, T t) { return (n.Value() + q.Value()) * u / t; } void Show() const { q.show(); n.show();} };
将模板用作参数
STL的实现就比较多的使用该功能。如下述代码所示,
<typename T>
表示一种模板类型。template <template <typename T> class Thing> class Crab { private: Thing<int> s1; Thing<double> s2; public: Crab() {}; // assumes the thing class has push() and pop() members bool push(int a, double x) { return s1.push(a) && s2.push(x); } bool pop(int & a, double & x){ return s1.pop(a) && s2.pop(x); } };
模板类和友元
模板的友元分3类:
- 非模板友元
- 约束模板友元,即友元的类型取决于类被实例化时的类型
- 非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元
-
模板类中的非模板友元函数
template <typename T> class HasFriend { private: T item; public: HasFriend(const T & i) : item(i) {ct++;} ~HasFriend() {ct--; } friend void counts(); friend void reports(HasFriend<T> &); // template parameter }; // non-template friend to all HasFriend<T> classes void counts() { cout << "int count: " << HasFriend<int>::ct << "; "; cout << "double count: " << HasFriend<double>::ct << endl; } // non-template friend to the HasFriend<int> class void reports(HasFriend<int> & hf) { cout <<"HasFriend<int>: " << hf.item << endl; } // non-template friend to the HasFriend<double> class void reports(HasFriend<double> & hf) { cout <<"HasFriend<double>: " << hf.item << endl; }
-
模板类的约束模板友元函数
// template prototypes template <typename T> void counts(); template <typename T> void report(T &); // template class template <typename TT> class HasFriendT { private: TT item; static int ct; public: HasFriendT(const TT & i) : item(i) {ct++;} ~HasFriendT() { ct--; } friend void counts<TT>(); friend void report<>(HasFriendT<TT> &); };
注意:从上述代码可以看出,需要在类定义的前面声明每个模板函数;另外由于
counts
函数没有用来推断类型的具体化的函数参数,因此在使用的时候需要使用如下形式:counts<int>();
-
模板类的非约束模板友元函数
template <typename T> class ManyFriend { private: T item; public: ManyFriend(const T & i) : item(i) {} template <typename C, typename D> friend void show2(C &, D &); }; template <typename C, typename D> void show2(C & c, D & d) { cout << c.item << ", " << d.item << endl; }
模板别名(C++11)
template<typename T> using arrtype = std::array<T, 12>;
这将arrtype定义为一个模板别名,可使用它来指定类型,如下代码所示:
arrtype<double> gallons; arrtype<int> days; arrtype<std::string> months;