16.1.1 函数模板
定义一个通用的函数模板,用于两个对象的比较
template<typename T> // 模板参数列表
int compare(const T& v1, const T& v2) {
if (v1 < v2) return -1; // v1小于v2返回-1
if (v2 < v1) return 1; // v1大于v2返回1
return 0; // 相等返回0
}
T
的实际类型在编译时根据compare
的使用情况来确定
实例化函数模板
// 实例化出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>
类型参数可以用来指定返回类型或函数的参数类型
template<typename T>
T foo(T* p) {
T tmp = *p; // tmp的类型将是指针p指向的类型
...
return tmp;
}
不能省略class
或typename
// 错误,U之前必须加上clas或template
template<typename T, U> T calc(const T&, const U&);
// 正确,在模板参数列表中,typename和class是一样的
template<typename T, class U> T calc(const T&, const U&);
可以在模板中定义非类型参数,表示一个值而非一个类型
例如,数组不能传递维度,所以将模板参数定义为整型参数,让函数自动推断数组的维度
template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M]) {
return strcpy(p1, p2);
}
compare("hi", "mom");
// int compare(const char (&p1)[3], const char (&p2)[4]);
// 加上字符串后面的'\0'
如果要加inline
和constexpr
,需要放在模板参数列表后面
template<typename T> inline
T min(const T&, const T&);
模板程序应该尽量减少对实参类型的要求
可以只用<
运算符的话,就不要用>
运算符,因为有些类可能只重载了<
运算符,而没有重载>
运算符
也可以调用标准库函数
template<typename T>
int compare(const T& v1, const T& v2) {
if (less<T>()(v1, v2)) return -1;
if (less<T>()(v2, v1)) return 1;
return 0;
}
这样指针也能用
补充:
- 静态内存或栈内存
分配在静态内存或栈内存中的由编译器自动创建和销毁
- 静态内存用来保存局部
static
对象、类static
数据成员以及定义在任何函数之外的变量。static
对象在使用之前分配,在程序结束时销毁 - 栈内存用来保存定义在函数内的非
static
对象。栈对象仅在其定义的程序块进行时才存在
- 动态内存
程序用堆来存储动态分配的对象,就是那些在程序运行时分配的对象,动态对象的生存期由程序来控制,当动态对象不再使用时,我们的代码必须显式地销毁它们
16.1.2 类模板
template<typename T>
class Blob {
public:
typedef T value_type;
typedef typename vector<T>::size_type size_type;
Blob();
Blob(initializer_list<T> il);
// Blob中的元素数量
size_type size() const { return data->size(); }
// 添加和删除元素
void push_back(const T& t) { data->push_back(t); }
// 移动版本
void push_back(T&& t) { data->push_back(std::move(t)); }
void pop_back();
// 元素访问
T& back();
T& operator[](size_type i);
private:
shared_ptr<vector<T>> data;
void check(size_type i, const string& msg) const;
};
实例化模板
Blob<int> ia; // 空的Blob
Blob<int> ia2 = {0, 1, 2, 3, 4}; // 有5个元素的Blob
类模板的成员函数
template<typename T>
T& Blob<T>::back() {
check(0, "back on empty Blob"); // 没有元素报错
return data->back();
}
template<typename T>
T& Blob<T>::operator[](size_type i) {
check(i, "subscript out of range"); // 下标越界
return (*data)[i];
}
template<typename T>
void Blob<T>::pop_back() {
check(0, "pop_back on empty Blob");
data->pop_back();
}
Blob
构造函数
创建一个vector
的对象,返回指向vector
的指针,用来初始化data
成员
template<typename T>
Blob<T>::Blob(): data(make_shared<vector<T>>()) {}
template<typename T>
Blob<T>::Blob(initializer_list<T> il): data(make_shared<vector<T>>(il)) {}
为了使用这个构造函数,我们必须传递给它一个initializer_list
,其中的元素必须与Blob
的元素类型兼容
Blob<string> articles = {"a", "an", "the"};
- 默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化
- 在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板参数
当我们在类模板外定义其成员时,我们并不在类的作用域中,直到遇到类名才表示进入类的作用域
// 重载后置++运算符
template<typename T>
BlobPtr<T> BlobPtr<T>::operator++(int) {
BlobPtr ret = *this; // 等价于 BlobPtr<T> ret = *this;
++*this;
return ret;
}
返回类型位于类的作用域之外,所以用加模板参数,函数体在类的作用域之内,所以不用加模板参数
类模板和友元
一对一友好关系
// 前置声明,在Blob中声明友元所需要的
template<typename T> class BlobPtr;
template<typename T> class Blob; // 运算符==中的参数所需要的
template<typename T>
bool operator==(const Blob<T>&, const Blob<T>&);
template<typename T> class Blob {
// 每个Blob实例将访问权限授予相同类型实例化的BlobPtr和相等运算符
friend class BlobPtr<T>;
friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
};
Blob<char> ca; // BlobPtr<char>和operator==<char>都是本对象的友元
Blob<int> ia; // BlobPtr<int>和operator==<int>都是本对象的友元
T
类型不同的就不是友元了
通用和特定的模板友好关系
先看一种情况
template<typename T> class Pal;
class C {
friend class Pal<C>;
template<typename T> friend class Pal2;
};
C
是一个普通的非模板类,用类C
去实例化的Pal<C>
类型是C
的友元,所以这里需要先声明Pal
,一对一的关系
Pal2
的所有类型都是C
的友元,无需前置声明,一对多的关系
再看另一种情况
template<typename T> class Pal;
template<typename T> class C2 {
friend class Pal<T>;
template<typename X> friend class Pal2;
friend class Pal3;
};
C2
本身是一个模板类,与C2
实例类型相同的Pal
才是C2
实例的友元,需要前置声明,是一对一的关系
Pal2
的所有类型都是C2
的所有实例的友元,多对多的关系
Pal3
是非模板类,他是C2
所有实例的友元,多对一的关系
- 为了让所有实例成为友元,友元声明中必须使用与类模板本身不同的模板参数
令模板自己的类型参数成为友元
template<typename Type> class Bar {
friend Type; // 将访问权限授予用来实例化Bar的类型
};
模板类型别名
template<typename T> using twin = pair<T, T>;
twin<string> authors; // pair<string, string>
可以固定一个或多个模板参数
template<typename T> using partNo = pair<T, unsigned>;
partNo<string> book; // pair<string, unsigned>
类模板的static成员
template<typename T> class Foo {
public:
static size_t count() { return ctr; }
private:
static size_t ctr;
};
Foo<string> fs;
// 实例化static成员Foo<string>::ctr和Foo<string>::count()
可以将static
成员定义为模板
template<typename T>
size_t Foo<T>::ctr = 0; // 定义并初始化ctr
通过类来访问static
成员,必须引用一个特定的类型
Foo<int> fi; // 实例化Foo<int>和static成员ctr
auto ct = Foo<int>::count(); // 实例化Foo<int>::count()
ct = fi.count(); // 使用Foo<int>::count()
ct = Foo::count(); // 错误,不知道是哪个实例的count
16.1.3 模板参数
作用域
typedef double A;
template<typename A, typename B>
void f(A a, B b) {
A tmp = a; // tmp的类型为A的类型,不是double
double B; // 错误,重声明模板参数
}
函数作用域里面的A
是模板参数,隐藏了外层的typedef
template<typename V, typename V> // 错误,非法重用模板参数名V
与函数参数相同,声明中的模板参数的名字不必与定义中相同
使用类的类型成员
如果希望使用一个模板参数的类型成员,就必须显式地告诉编译器该名字是一个类型
template<typename T>
typename T::value_type top(const T& c) {
if (!c.empty())
return c.back();
else
return typename T::value_type();
}
这里T
可能是一个容器vector<int>
,而要返回是一个元素int
,所以需要显式说明返回值是T
里面的元素类型
如果不加typename
,则类名+作用域运算符默认是访问类的成员,而不是类型
当我们希望通知编译器一个名字表示类型时,必须使用
typename
,而不能使用class
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;
}
bool i = compare(0, 42); // 使用less<int>
Sales_date item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn); // 自定义的比较函数
只要有一个默认实参,则剩下的所有参数都要有默认实参
16.1.4 成员模板
一个类可以包含本身是模板的成员函数,称为成员模板,不能是虚函数
普通(非模板)类的成员模板
class DebugDelete {
public:
DebugDelete(ostream& s = cerr): os(s) {}
template<typename T> void operator()(T* p) const {
os << "deleting unique_ptr" << endl;
delete p;
}
private:
ostream& os;
};
double *p = new double;
DebugDelete d;
d(p); // 调用operator(double*),释放double型指针
int *ip = new int;
DebugDelete()(ip); // 使用临时对象调用operator(int*),释放int型指针
DebugDelete
中重载括号运算符是一个成员模板,不管输入的是什么类型,都可以delete
掉
类模板的成员模板
如果类和成员函数都是模板,要先写类的模板参数,再写成员函数的模板参数
template<typename T> class Blob {
template<typename It> Blob(It b, It e);
// ...
};
template<typename T> // 类的类型参数
template<typename It> // 构造函数的类型参数
Blob<T>::Blob(It b, It e): data(make_shared<vector<T>>(b, e)) {}
实例化与成员模板
int ia[] = {0, 1, 2, 3, 4, 5};
Blob<int> a1(begin(ia), begin(ia)); // 实例化Blob<int>以及接受两个int*参数的构造函数
vector<long> vi = {0, 1, 2, 3, 4, 5};
Blob<long> a2(vi.begin(), vi.end()); // 实例化Blob<long>以及接受两个vector<long>::iterator参数的构造函数
list<const char*> w = {"now", "is", "the", "time"};
Blob<string> a3(w.begin(), w.end()); // 实例化Blob<string>以及接受两个list<const char*>::iterator参数的构造函数