定义模板
模板是泛型编程的基础,容器、迭代器和算法都是模板的例子。一个模板就是一个创建类或函数的蓝图或者公式。
假定希望编写一个函数来比较两个值,并指出第一个值是小于、等于还是大于第二个值。在实际中,我们可能想要定义多个函数,每个函数比较一种给定类型的值。
//如果两个值相等返回0,如果v1小于v2返回-1,否则返回1
int compare(const string &v1, const string &v2) {
if (v1 < v2) return -1;
if (v1 > v2) return -1;
return 0;
}
int compare(const double &v1, const double &v2) {
if (v1 < v2) return -1;
if (v1 > v2) return 1;
return 0;
}
可以看出这两个函数几乎是相同的,唯一的差异是参数类型,函数体完全一样。
如果每种类型都要定义完全一样的函数体就非常麻烦,更麻烦的是还要确定可能要compare的所有类型。
1函数模板
我们可以定义一个通用的函数模板,而不是为每个类型都定义一个新函数。一个函数模板就是一个公式,可用来生成针对特定类型的函数版本。如:
template <typename T>
int compare(const T &v1, const T &v2) {
if (v1 < v2) return -1;
if (v1 > v2) return 1;
return 0;
}
模板定义以关键字template开始,后跟一个模板参数列表,模板参数列表是一个逗号分割的一个或多个模板参数的列表,用<>包围起来。如上面只有一个模板参数的模板参数列表<typename T>.
模板参数表示在类或函数定义中用到的类型或值。如compare函数中,我们用T表示一个类型,而T表示的实际类型则是根据compare的使用情况来决定。
①实例化函数模板
当我们调用一个函数模板时,编译器用函数实参来为我们推断模板实参。
如:
cout << compare(1, 0) << endl; //T的类型为int
实参类型为int,编译器会推断出模板实参为int。
编译器用推断出的模板参数来为我们实例化一个特定的版本的函数。当编译器实例化一个模板时,它使用的模板实参代替对应的模板参数来创建出模板的一个新实例。
如:
//实例化出int compare(const int&, const int&)
cout << compare(1, 0) << endl; //T为int
//实例化出int compare(const vector<int> &, const vector<int>&)
vector<int> vec1{1, 2, 3}, vec2{4, 5, 6};
cout << compare(vec1, vec2) << endl; //T为vector<int>
②模板类型参数
compare有一个模板类型参数。我们可以将类型参数当作类型说明符就像内置类型或类类型说明符一样使用。类型参数可以用来指定返回类型或函数的参数类型,以及在函数体类用于声明变量或类型转换。类型参数前必须加上关键字typename或class,这两个含义相同。
如:返回两个容器中size较小的一个
template <typename T>
T retShort(const T &s1, const T &s2) {
if (s1.size() < s2.size()) {
return s1;
} else {
return s2;
}
}
int main() {
string s1("abc"), s2("abcd");
vector<int> vec1{ 1, 2, 3 }, vec2{ 1, 2, 3, 4, 5 };
cout << retShort(vec1, vec2).size() << endl
<<retShort(s1, s2);
}
//结果为:
3
"abc"
③非类型模板参数
可以在模板中定义非类型参数,非类型参数表示一个值而非一个类型。当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替,这些值必须是常量表达式。
如:比较两个字符串常量
template<int N, int M>
int compare(const char(&p1)[N], const char(&p2)[M]) {
return strcmp(p1, p2);
}
int main() {
//实例化出如下版本:
//int compare(const char (&p1)[3], const char (&p2)[4])
cout << compare("abc", "abcd");
}
//结果为:
-1
2.类模板
类模板是用来生成类的蓝图的。与函数模板不同,编译器不能为类模板推断出参数类型。为了使用类模板,必须在模板名后的尖括号中提供额外信息,用来代替模板参数的模板实参列表。
①定义类模板
例如:我们之前写过类似shared_ptr<string>的类HasPtr,现在我们让它实现shared_ptr的功能。
类似函数模板,类模板以关键字template开始,后跟模板参数列表。
template <typename T>
class HasPtr {
public:
HasPtr() :_ps(new T()), _use(new int(1)) {}
HasPtr(const HasPtr& rhs) :_ps(rhs._ps), _use(rhs._use) {
++*_use;
}
HasPtr(const T &t) :_ps(new T(t)), _use(new int(1)) {}
HasPtr& operator=(const HasPtr& rhs) {
if (--_use == 0) {
delete _use;
delete _ps;
}
_use = rhs._use;
_ps = rhs._ps;
++*_use;
return *this;
}
~HasPtr() {
if (--_use == 0) {
delete _use;
delete _ps;
}
}
private:
T* _ps;
int* _use;
};
②实例化类模板
当使用一个类模板时,必须显式模板实参列表,它们被绑定到模板参数。编译器使用这些模板实参来实例化出特定的类。
例如:实现shared<string>的类
HasPtr<string> p("abc"); //指向string("abc")的HasPtr<string>
p使用的特定类型版本的HasPtr(HasPtr)。 编译器会实例化出一个与下面等价的类:
template <>
class HasPtr<string> {
public:
HasPtr() : _ps(new string()), _use(new int(1)) {}
HasPtr(const HasPtr& rhs) :_ps(rhs._ps), _use(rhs._use) {
++*_use;
}
HasPtr(const string &t) :_ps(new string(t)), _use(new int(1)) {}
HasPtr& operator=(const HasPtr& rhs) {
if (--_use == 0) {
delete _use;
delete _ps;
}
_use = rhs._use;
_ps = rhs._ps;
++*_use;
return *this;
}
~HasPtr() {
if (--_use == 0) {
delete _use;
delete _ps;
}
}
private:
string* _ps;
int* _use;
};
一个类模板的每个实例都会形成一个独立的类。类型HasPtr与任何其他HasPtr类型都没有关联,也不会对任何其他Blob类型的成员有特殊的访问权限。
③类模板的成员函数
我们既可以在类模板内部,也可以在类模板外部为其定义成员函数。但是,在外部定义一个成员是,必须说明成员属于哪个类。而且,从一个模板生成的类的名字中必须包含其模板的实参。
如:HasPtr默认构造函数的类外定义
HasPtr<T>::HasPtr(): _ps(new string()), _use(new int(1)) {}