类型作为模板参数,我们用T代替——T是一个参数,能被赋予double、int、string、vector<Record>和Window*之类的“值”。在C++中引入类型参数T的语法为template<typename T>前缀,其含义是“对所有类型T”。例如:
template<typename T>
class vector{
int sz; //大小
T* elem; //指向元素的指针
int space; //大小 + 空闲空间
public:
vector():sz{0}, elem{nullptr}, space{0}{}
explicit vector(int s):sz{s}, elem{new T[s]}, space{s}
{
for(int i=0;i<sz;i++) elem[i]=0; //元素被初始化
}
vector(const vector&); //拷贝构造函数
vector& operator=(const vector&); //拷贝赋值
vector(vector&&); //移动构造函数
vector& operator=(vector&&); //移动赋值
~vector(){delete[] elem;} //析构函数
T& operator[](int n){return elem[n];} //访问:返回引用
const T& operator[](int n){return elem[n];}
int size() const {return sz;} //当前大小
int capacity() const {return space;}
void resize(int newsize); //增长
void push_back(const T& d);
void reserve(int newalloc);
};
我们可以像下面这样使用模板vector:
vector<double> vd; //T为double
vector<int> vi; //T为int
vector<double*> vpd; //T为double*
vector<vector<int>> vvi; //T为vector<int>, 其中T为int
我们有时称类模板为类型生成器,称由一个类模板按给定的模板实参生成类型(类)的过程为特例化或模板实例化。例如,vector<char>和vector<Poly_line*>被称为vector的特例化版本。
在使用这种类模板成员函数时,编译器将生成适合的函数。例如,当编译器遇到v.push_back("Norah")时,它会根据模板定义
template<typename T> void vector<T>::push_back(const T& d) {/*...*/);
生成函数
void vector<string>::push_back(const string& d){/*...*/);
泛型编程:编写能够正确处理以参数形式呈现的各种类型的代码,只要这些参数类型满足特定的语法和语义要求。
我们应该:
- 在对性能要求高的场合使用模板。
- 在需要灵活组合不同类型信息的场合中使用模板。
C++14提供了一种机制,可以极大地改善模板接口的检查。例如:
//C++11中无法准确陈述对实参T的期望
template<typename T> //对所有类型的T
class vector{
//...
};
//C++14中则增加了显示陈述
template<typename T> //对所有类型的T
requires Element<t>() //对所有满足Element的类型T
class vector{
//...
};
//显示陈述可简化为
template<Element T> //对所有令Element<T>()为true的类型T
class vector{
//...
};
对于任意模板C,“D是B”并不意味着“C<D> 是 C<B>”。
本质上,任何类别的实参都是有用的,但实际上我们只考虑类型和整数作为参数。
template<typename T, int N> struct array{
T elem[N]; //在成员数组中保存元素
//依赖于默认构造函数、析构函数和赋值操作
T& operator[](int n); //访问:返回引用
constT& operator[](int n) const;
T* data(){return elem;) //转换为T*
const T* data() const {return elem;}
int size() const{return N;}
};
我们可以像下面这样使用array
array<int, 256> gb; //256个整数
array<double, 6> ad = {0.0, 1.1, 2.2, 3.3, 4.4, 5.5};
const int max = 1024;
void some_fct(int n)
{
array<char, max> loc;
array<char, n> oops; //错误:编译器不知道n的值
//...
array<char, max> loc2 = loc; //创建副本作为备份
//...
loc = loc2; //恢复数据
//...
}
对于一个类模板,当你创建特定类的对象时,需要指定模板实参。例如:
array<char, 1024> buf; //对buf, T是char 且 N是1024
array<double, 10> b2; //对b2, T是double 且 N是10
对于函数模板,编译器通常能够根据函数实参判断出模板参数。例如:
template<class T, int N> void fill(array<T, N>& b, const T& val)
{
for (int i=0;i<N;++i) b[i] = val;
}
void f()
{
fill(buf, 'x'); //对fill(), T是char且N是1024
fill(b2, 0.0); //对fill(), T是double且N是10
}
在技术上,fill(buf, 'x')是fill<char, 1024>(buf, 'x')的简写。fill(b2,0)是fill<double, 10>(b2,0)的简写。
我们可以不必再代码中添加复杂的try...catch语句就能有效处理潜在的资源泄露问题。资源应由构造函数获取,由析构函数释放。
在<memory>标准库中提供了unique_ptr,它是一种存储指针的对象。当销毁unique_ptr时,它会delete所指向的对象。但它有一个重要的限制:你不能将一个unique_ptr对象赋予另一个unique_ptr从而将它们指向相同的对象。