1.模板参数
(1).模板参数的内容
模板参数完整描绘了模板的特性,每个模板参数描述了下面内容之一
a).类型 c++自带的类型,或者是自定义类型
b).编译时常量
c).其他模板
(2).无类型模板参数
这是指的是类型是未知的,在实例化的时候才产生实例类
比如:创建一个Stack类
template<class T, size_t N>
class Stack{
T data[N];
size_t count;
};
在创建一个Stack实例的时候,就必须指明N的大小,比如
Stack<int, 100> mySatck;
(3)默认模板参数
类模板可以设定一个默认的参数,但是函数模板不可以。比如:
template<class T, size_t N = 100>
class Stack{
T data[N];
size_t count;
public:
void push(const T& t);
//......
};
也可以给类型指定默认值
template<class T = int, size_t N = 100>
class Stack{
T data[N];
size_t count;
public:
void push(const T& t);
//......
};
如果不指定类型和数值,则使用默认值。对于没有默认指定的部分,则需要指定。比如对于第一个模板类
实例化则需要 Stack<int> mystack; 或者更改默认值 Stack<double, 999> mystack;
第二个实例化,则可以 Stack<char> mystack或者 Stack<> mySatck; 或者 Stack<int, 188> mystack;
(4).模板类型的模板参数
比如:先创建一个模板类
template<class T>
class Array{
T data[N];
}
然后以创建的模板类当作模板的参数
template<class T, template<class> class Seq>
class Container{
private:
Seq<T> seq;//这里会把Seq解释为模板
}
模板也可以写为:template<class T1, template<class T2> class Seq>这里T2并不是必须的
可以理解为模板参数只需要有提供的即可
//比如:
//1.模板数组类
template<class T, size_t N>
class Array{
T data[N];
int count;
};
//2,包含模板数组的模板类,这个类含有一个常量:
template<class T, size_t N, template<class, size_t>class Seq>
class container{
Seq<T,N> seq;
};
这里的模板参数中,模板的类型和值都没有给出,这就如同函数的定义一样,是一个形式参数
为了实现常量,需要在模板中给出N,以便在类中初始化
(4.1) 模板类型的模板参数中含有默认值
//1.一个模板数组的声名,其中含有默认值
template<class T, size_t N = 10>
class Array{
T data[N];
int count;
};
//2.含有上面模板数组的模板类
template<class T, template<class, size_t = 10> class Seq>
class Container{
Seq<T> seq;//这里使用了默认值
};
当然也可以声名为
template<class T, size_t N, template<class, size_t = 10> class Seq>
class Container{
Seq<T,N> seq;
};
这里的默认值大小10是必须的.
模板作为参数的时候,有时候需要指定类型,比如分配器:
template<class T, template<class U, classs = allocate<U> >class Seq >
class Container{
}
这里指定了U类型,这是因为分配器需要保持分配的类型与模板提供的类型相匹配
2. typename 的解释
#include <iostream>
using namespace std;
template<class T>
class X{
typename T::id i;
public:
void f(){
cout << "class X" << endl;
i.g();
}
};
class Y{
public:
class id{
public:
void g(){
cout << "class Y" << endl;
}
};
};
int main(int argc, char** argv) {
X<Y> xy;
xy.f();
return 0;
}
这里的 typename T::id 是类型T中是否有一个叫做id的标识符,然后创建同类型的i对象。
如果id是一个静态成员,则可以对它直接进行操作,但是不能创建同类型的i
这个规则是:如果一个模板代码内部的某个类型被模板类型参数所限定,则必须使用关键字typename作为前缀进行说明,除非他已经出现在
基类的规格说明中,或者它出现在同一作用域范围内的初始化列表中。
案例如下:
//创建一个模板类-模板参数是T,以及模板类Seq,模板参数为类型U和默认类型allocator<U>
template<class T, template<class U, class = allocator<U> >class Seq>
class Container{
private:
Seq<T> seq;
public:
void push_back(const T& t){
seq.push_back(t);
}
typename Seq<T>::iterator begin(){
return seq.begin();
}
typename Seq<T>::iterator end()
{
return seq.end();
}
};
int main(int argc, char** argv) {
Container<int, vector> vContainer;
for(int i = 0; i < 10; i++)
vContainer.push_back(i);
cout << *vContainer.begin() << endl;
cout << *(vContainer.end()-1)<< endl;
return 0;
}
2.1
标准序列容器都有一个默认的分配器参数,传递这些序列容器中的一个作为模板参数,创建一个新类。
按照之前的解释,这里的Seq被模板参数所限定,所以在引用他的迭代器的时候,需要指定typename
typename并不是创建了一个新的类型,而是通知编译器,被限定的表示符表示一个类型
typename Seq<T>::iterator it;
代表it的类型是 Seq<T>::iterator
2.2 typename也可以用来代替模板的class
2.3 template关键字可以作为提示
template<class charT, size_t N>
basic_string<charT> bitsetToString(const bitset<N>& bs)
{
return bs. template to_string<charT, char_traits<charT>, allocator> >();
}
这里的template代表to_string是一个函数模板,它的<>不是大于或者小于符号
2.4 成员模板
bitset::to_string()函数模板是一个成员模板的例子,在一个类或者类模板中声名一个模板。它允许一些独立的模板参数结合,以便组合使用
比如: complex类定义
template<typename T>
class complex{
public:
template<class X> complex(const complex<X>&);
}
complex类中的拷贝构造函数使用了其他的类型,比如:
complex<float> z(1.2);
complex(double) w(z);
z是float类,w是double类,这种方法使得类型转换更加灵活
但是在类外也需要反应这种嵌套的时候,就必须把类外的定义放在前面,函数模板的定义放在后面:
template<class T>
template<class X>
complex(const complex<X>& c)
成员函数模板在标准库中也有这样的:
template<class InputIterator>
vector(InputIterator first, InputIterator last, const Allocator& = Allocator());
2.5 函数模板的几个问题
(1).类型推断
下面这个函数返回a,b中小的那个
template<typename T>
T min(T a, T b);
a)调用方式: int c = min(1,2); T被推断为整型;
也可以这么调用 int c = min<int>(1,2);这种是显式调用,模板把类型推断为int,如果传入的参数是double,也会被转换为int
b)int c = min(1, 3.14)这种调用方式就是错误的,因为参数的类型不一致,我们可以使用显式调用
c)对于只有返回值类型是模板的,需要显式调用。
(2)函数模板重载
可以命名同名的函数模板,但是使用的时候编译器会尽量避免二义性,调用参数最接近的函数,如果无法避免,则会报错
(3).以一个以生成的函数模板地址作为参数
3.模板特化
1.显式特化
这个前面已经说过了。但是对于类的特化,应用会更加灵活,比如对vector有一个vector<bool>的特化
首先声名的是vector类
template<class T, class Allocator = allocator<T> >
class vector{
......
};
为上面的模板进行bool的特化
template<> class vector<bool, allocator<bool> >{
......
}
2.半特化
类模板也可半特化,比如bool类型的vector可以定义为:
template<class Allocator>
class vector<bool, Allocator>;
这样后面一部分Allocator还是灵活的,这就是半特化,选择一种合适的模板实例化也遵循"特化程度最高"
4.模板和友元
#include <iostream>
using namespace std;
template<class T> class Friendly;//提前声名模板类
template<class T> void f(const Friendly<T>&);//提前声名函数模板
template<class T>
class Friendly{
T t;
public:
Friendly(const T& theT) : t(theT){
}
friend void f<>(const Friendly<T>&);//这里必须使用<> 表示要用模板函数
void g(){
f(*this);
}
};
void h(){
f(Friendly<int>(1));
}
//实现友元函数
template<class T>
void f(const Friendly<T>& fo){
cout << fo.t << endl;
}
int main(int argc, char** argv) {
h();
Friendly<int>(2).g();
return 0;
}
//除了提前声名,还可以在类中声名友元函数,但是如此声名就不再是模板函数,而是一个普通函数
friend void f(const Friendly<T>& fo);
//案例2
#include <iostream>
using namespace std;
template<class T> class Box;//声名一个模板类
template<class T> //重载+运算符友元函数
Box<T> operator+(const Box<T>&, const Box<T>&);
template<class T>//重载输出
ostream& operator<<(ostream& out, const Box<T>&);
template<class T>
class Box{
private:
T t;
public:
Box(const T& theT){
t = theT;
}
friend Box<T> operator+ <> (const Box<T>&, const Box<T>&);//这里
friend ostream& operator<< <> (ostream& out, const Box<T>& obj);
};
template<class T>
Box<T> operator+(const Box<T>& b1, const Box<T>& b2)
{
return Box<T>(b1.t + b2.t);
}
template<class T>
ostream& operator<<(ostream& out, const Box<T>& obj){
return out << obj.t;
}
int main()
{
Box<int> b1(10);
Box<int> b2(20);
cout << b1 + b2 << endl;//成立,可以调用重载+
cout << b1 + 2 << endl;//错误,因为模板没有提供这样的隐式转换方法
}
而使用内部类,则可以进行转换,
友元模板:在程序中可以使用更精确的模板,如果使用一个精确的类型,则可以认为这个函数是这个类所有实例的友元函数
5.模板的特征
这是一种将与某种类型相关联的所有声明绑定在一起的实现方式,实现了类型和值的灵活匹配
例子:
#include <iostream>
using namespace std;
//设置不同的食物
class Milk{
public:
friend ostream& operator<< (ostream& out, const Milk& obj){
return out << "Milk";
}
};
class CondenseMilk{
public:
friend ostream& operator<< (ostream& out, const CondenseMilk& obj){
return out << "CondenseMilk";
}
};
class Honey{
public:
friend ostream& operator<< (ostream& out, const Honey& obj){
return out << "Honey";
}
};
class Cookies{
public:
friend ostream& operator<< (ostream& out, const Cookies& obj){
return out << "Cookies";
}
};
//不同的人物类
class Bear{
public:
friend ostream& operator<< (ostream& out, const Bear& obj){
return out << "Bear";
}
};
class Boy{
public:
friend ostream& operator<< (ostream& out, const Boy& obj){
return out << "Boy";
}
};
//创建一个空的特性类-作为模板
template<class Guest>
class GuestTraits;
//创建一个Bear的特化模板 -- 这是将Beaer类与两种食物类型结合在一起
template<> class GuestTraits<Bear>{
public:
typedef CondenseMilk beverage_type;
typedef Honey snack_type;
};
//创建一个Boy类的特化模板
template<> class GuestTraits<Boy>{
public:
typedef Milk beverage_type;
typedef Cookies snack_type;
};
//创建一个顾客类,里面绑定两种食物类,如果有需要,可以修改
class MixedUpTraits{
public:
typedef Milk beverage_type;
typedef Honey snack_type;
};
//创建一个模板类-将顾客与顾客特性类绑定
template<class Guest, class traits = GuestTraits<Guest> >
class BearCorner{
Guest theGuest;//顾客
typedef typename traits::beverage_type beverage_type;//特性1类型
typedef typename traits::snack_type snack_type;//特性2类型
beverage_type bev;//特性1
snack_type snack;//特性2
public:
BearCorner(const Guest& g):theGuest(g){
}
void entertain(){
cout << "Entertain " << theGuest << " "
<< "serving " << bev << " "
<< "and " << snack
<< endl;
}
};
int main(){
//前面使用默认的模板,分别给Bear和Boy创建模板
Boy cr;
BearCorner<Boy> pc1(cr);
pc1.entertain();
Bear pb;
BearCorner<Bear> pc2(pb);
pc2.entertain();
//修改特性模板 -- 没有使用默认的特性模板
BearCorner<Bear, MixedUpTraits> pc3(pb);
pc3.entertain();
return 0;
}
(5.2)策略
实际上,特征类是为特征而设计的,在相关的特征类之间的改变,通常就是类型和常数值,或者是相关类型的模板参数的固定算法。通常特征类本身就是模板,在
上个例子中,如果加入新的函数,比如点餐,然后封装在特征类之中,这种方法叫做策略
(5.3)模板递归
案例如下,构造一个能够跟踪对象数量的类
#include <iostream>
using namespace std;
template<class T>
class Count{
private:
static int count;
public:
Count(){
count++;
}
~Count(){
count--;
}
const static int getCount(){
return count;
}
};
template<class T>
int Count<T>::count = 0;
//继承计数器类
class CountedClass1 : public Count<CountedClass1>{
};
class CountedClass2 : public Count<CountedClass2>{
};
//继承类以自己作为模板参数,看起来就如同模板递归一样
//实际上,用不同的类型作为模板的参数,初始的count都等于0
//也就是不同类型的模板
int main(){
CountedClass1 a;
CountedClass2 a2;
cout << a.getCount() << endl;
CountedClass2 b;
cout << a.getCount() << endl;
cout << b.getCount() << endl;
return 0;
}
6.模板元编程
如下面的程序,是一个递归的模板程序,这个程序的结果实际上在编译期间就已经计算出来了
#include <iostream>
using namespace std;
template<int n>
struct Factorial{
enum { val = Factorial<n-1>::val * n};
};
template<> struct Factorial<0>{
enum { val = 1};
};
int main()
{
cout << Factorial<12>::val << endl;
return 0;
}
模板元编程就是利用编译期间生成的代码进行编程的技术
比如上面的程序,可以用半特化来写终止递归的条件
6.1 编译时编程
利用模板的递归可以实现循环,比如求一个正数的n次方
#include <iostream>
using namespace std;
int power(int n, int p){
int sum = 1;
while(p > 0){
sum *= n;
p--;
}
return sum;
}
template<int N, int P>
class Power{
public:
enum { val = N * Power<N, P-1>::val};
};
//半特化设定终止条件
template<int N>
class Power<N, 0>{
public:
enum { val = 1};
};
int main(){
cout << power(10,3) << endl;
cout << Power<10,3>::val << endl;
}
把模板当作函数一样进行传递的例子:
#include <iostream>
using namespace std;
//实现的表达式是:
//f(n) + f(n-1) + f(n-2) + ... + f(0)
template<int n, template<int> class F>
struct Accumulate{
enum{val = Accumulate<n - 1, F>::val + F<n>::val};
};
template<template<int> class F>
struct Accumulate<0, F>{
enum {val = F<0>::val};
};
template<int n>
struct f1{
enum{ val = n};
};
int main(){
cout << Accumulate<5,f1>::val << endl;
}
6.2 循环分解
可以减少循环的次数
6.3 编译时选择
6.4 编译时断言
如下面的例子所言:
#include <iostream>
using namespace std;
template<bool>
struct StaticCheck{
StaticCheck(...);//构造函数,默认生成true的模板
};
template<>
struct StaticCheck<false>{
};
#define STATIC_CHECK(expr, msg){\
class Error_##msg{ };\
sizeof((StaticCheck<expr>(Error_##msg())));\
}
//函数模板
template<class To, class From>
To safe_cast(From from)
{
STATIC_CHECK(sizeof(From) <= sizeof(To), NarrowingConversion);
return reinterpret_cast<To>(from);
}
int main(){
void* p = 0;
int i = safe_cast<int>(p);
cout << "int cast okay" << endl;
return 0;
}
函数模板safe_cast<>()用来检查两个对象的长度,检查from对象是否大于to的长度,如果小于to,则会在编译期间报错:ERROR_NarrowingConversion
错误会在编译期间出现,并且显示一个有效的信息。
StaticCheck类是一个特殊的类,在条件是真的时候,则生成true的模板,当条件不通过的时候,则生成false的模板
7 表达式模板
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cstddef>
using namespace std;
template<class, size_t>
class MyVectorSum;
//向量类
template<class T, size_t N> class MyVector{
T data[N];
public:
MyVector<T, N>& operator=(const MyVector<T, N>& right)
{
for(size_t i = 0; i < N; i++)
{
data[i] = right.data[i];
}
}
//用来处理两个向量相加
MyVector<T, N>& operator=(const MyVectorSum<T, N>& right);
const T& operator[](size_t i) const{
return data[i];
}
T& operator[](size_t i){
return data[i];
}
};
template<class T, size_t N> class MyVectorSum{
private:
const MyVector<T, N>& left;
const MyVector<T, N>& right;
public:
MyVectorSum(const MyVector<T, N>& lhs, const MyVector<T, N>& rhs) : left(lhs), right(rhs){
}
T operator[](size_t i) const{
return left[i] + right[i];
}
};
//支持 V1 = V2 + V3
template<class T, size_t N>
MyVector<T, N>& MyVector<T, N>::operator=(const MyVectorSum<T, N>& right){
for(size_t i = 0; i < N; i++)
data[i] = right[i];
return *this;
}
//仅仅是用来储存
template<class T, size_t N>
MyVectorSum<T, N> operator+(const MyVector<T,N>& left, const MyVector<T,N>& right){
return MyVectorSum<T, N>(left, right);
}
//对Myvector初始化
template<class T, size_t N>
void init(MyVector<T,N>& v)
{
for(size_t i = 0; i < N; i++)
v[i] = rand() % 100;
}
template<class T, size_t N>
void print(const MyVector<T, N>& v){
for(size_t i = 0; i < N; i++)
cout << v[i] << " ";
cout << endl;
}
int main(){
srand(time(0));
MyVector<int, 5> V1;
init(V1);
print(V1);
MyVector<int, 5> V2;
init(V2);
print(V2);
MyVector<int, 5> V3;
init(V3);
print(V3);
V3 = V1 + V2;
print(V3);
//V3 = V1 + V2 + V3;
return 0;
}
MyVector是一个模板向量类,当它执行V1 + V2 的时候 会调用MyVectorSum<T, N> operator+(const MyVector<T,N>& left, const MyVector<T,N>& right)
这时,它不会真的计算两个向量的和,而是生成一个新的对象MyVectorSum,它只是引用了两个向量,这个过程并没有产生新的对象,对内存的使用也没有增加多少
只有在使用 = 的时候,才会进行计算。
但是,这个程序无法计算 V1 + V2 + V3 ,这是因为 V1 + V2 生成了 MyVectorSum, 然后 V3 是MyVector无法进行相加
所以需要中间层,能使MyVectorSum与MyVector相加,可以使用模板类作为中间层
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cstddef>
using namespace std;
template<class, size_t, class, class>
class MyVectorSum;
template<class T, size_t N> class MyVector{
T data[N];
public:
MyVector<T, N>& operator=(const MyVector<T, N>& right)
{
for(size_t i = 0; i < N; i++)
{
data[i] = right.data[i];
}
}
template<class Left, class Right>
MyVector<T, N>& operator=(const MyVectorSum<T, N, Left, Right>& right);
const T& operator[](size_t i) const{
return data[i];
}
T& operator[](size_t i){
return data[i];
}
};
//抽象储存类
template<class T, size_t N, class Left, class Right> class MyVectorSum{
private:
const Left& left;
const Right& right;
public:
MyVectorSum(const Left& lhs, const Right& rhs) : left(lhs), right(rhs){
}
T operator[](size_t i) const{
return left[i] + right[i];
}
};
//支持 V1 = V2 + V3
template<class T, size_t N>
template<class Left, class Right>
MyVector<T, N>& MyVector<T, N>::operator=(const MyVectorSum<T, N, Left, Right>& right){
for(size_t i = 0; i < N; i++)
data[i] = right[i];
return *this;
}
//仅仅是用来储存
template<class T, size_t N>
MyVectorSum<T, N, MyVector<T,N>, MyVector<T,N> > operator+(const MyVector<T,N>& left, const MyVector<T,N>& right){
return MyVectorSum<T, N,MyVector<T,N>, MyVector<T,N> >(left, right);
}
template<class T, size_t N, class Left, class Right>
MyVectorSum<T, N, MyVectorSum<T,N,Left,Right>, MyVector<T,N> > operator+(const MyVectorSum<T,N, Left, Right>& left, const MyVector<T,N>& right){
return MyVectorSum<T, N, MyVectorSum<T,N, Left, Right>, MyVector<T,N> >(left, right);
}
//对Myvector初始化
template<class T, size_t N>
void init(MyVector<T,N>& v)
{
for(size_t i = 0; i < N; i++)
v[i] = rand() % 100;
}
template<class T, size_t N>
void print(const MyVector<T, N>& v){
for(size_t i = 0; i < N; i++)
cout << v[i] << " ";
cout << endl;
}
int main(){
srand(time(0));
MyVector<int, 5> V1;
init(V1);
print(V1);
MyVector<int, 5> V2;
init(V2);
print(V2);
MyVector<int, 5> V3;
init(V3);
print(V3);
V3 = V1 + V2;
print(V3);
V3 = V1 + V2 + V3;
print(V3);
return 0;
}
8.模板编译模型
模板和普通多文件编译不一样,由于泛型模板,会造成编译失败,所以一般是把定义和实现放在一起,这样的话,会暴露接口。
如果想要分开定义和实现,则需要使用包含模型或者分离模型
1.包含模型
定义模板之后,使用各种特化模型
2.分离模型
使用export关键字定义