1.数据类型表
是一种数据类型公开的能力,利用typedef这样的语句实现类型萃取器
模板名<模板实参>::类型名 变量名
这样使用的目的:
在不同模板之间,在类的外部形成数据访问能力
示例代码:
#include <iostream>
using namespace std;
template<typename T>
struct test
{
typedef T value_type;
typedef T& reference;
typedef T* pointer;
};
int main()
{
test<int>::value_type a = 100.9;
cout << a << endl;
test<double>::value_type b = 100.9;
cout << b << endl;
}
上述代码内嵌了数据类型,从模板传入的类型衍生出了其对象相关类型的能力,外部的数据类型和内部传入的数据类型保持一致,保证了动态识别内部的数据类型
2.traits规范了多模块之间的类型一致
如果一个模板中,全部的成员都是public的,那么这个模板就是一个独立的数据类型表,用来规范数据
示例代码:
#include <iostream>
using namespace std;
//第一步:定义一个规范类模板类型表的基类模板
template<typename T,typename U>
class TypeTbl
{
public:
typedef T value_type1;
typedef T reference1;
typedef U value_type2;
typedef U reference2;
};
//第二步:凡是继承了这个类型表的模板,它的访问类型就被确定
template<typename T, typename U>
class Test :public TypeTbl<T, U>
{
};
int main()
{
Test<int, double>::value_type1 a = 200;
Test<int, double>::reference1 b = a;
cout << a << endl;
cout << b << endl;
}
在STL库中设计人员经常使用这样的技巧
Binary类中就使用了这样的技巧,对二元函数的形参类型进行了约束
示例代码:
#include <iostream>
using namespace std;
template <typename _A1,typename _A2,typename _R>
struct Binary
{
typedef _A1 Arg1;
typedef _A2 Arg2;
typedef _R Ret;
};
//设计一个继承了binary接口的类模板
template <typename TR, typename T1, typename T2>
class Add :public Binary<TR, T1, T2>
{
public:
TR bfun(const T1& x, const T2& y)const
{
return x + y;
}
};
int main()
{
double a = 100.09, b = 20.2;
Add<double, double, double> addObj;
cout << addObj.bfun(a,b)<< endl;
//使用Arg1定义一个变量x
typename Add<int, int, double>::Arg1 x = 1000;
cout << x << endl;
}
分析:
因为Add包含了Binary的数据类型表,因此系统中的其他模块就可以使用Add::Arg1,Add::Arg2,Add::Ret这种方式和Add本身进行对接。
这种数据类型的抽象,达到了多个系统模块之间的类型统一
3.特化数据类型
示例代码:
class Test1
{
public:
char compute(int x, double y)
{
return x;
}
};
class Test2
{
public:
double compute(double x, double y)
{
return x;
}
};
//Test1和Test2都只有一个compute函数,而且函数逻辑也完全相同
//不同的仅仅是函数参数类型
//用模板来抽象Test1和Test2
template <typename Arg1,typename Arg2,typename Ret>
class Test
{
public:
Ret compute(Arg1 x, Arg2 y)
{
return x;
}
};
上述代码的分析:
没有很好的复用已经设计好的函数,在Test的设计中已经侵入了Test1和Test2的设计,能够把Test写出来,是直到Test1和Test2是怎么实现的,也就是要直到它传入的模板参数(比如给调用Test1版本的话传入<int,double,char>,而在调用Test2的时候传入<double,double,double>),而我们现在就不想知道它的实现,直接传入<Test1>和<Test2>来调用它们各自的版本的函数,写法如下:
#include <iostream>
using namespace std;
class Test1;
class Test2;
//两个类模板规范一个统一的接口
template <typename T>
class TypeTbl
{
};
//特化模板
template <>
class TypeTbl<Test1>
{
public:
typedef char ret_type;
typedef int par1_type;
typedef double par2_type;
};
template <>
class TypeTbl<Test2>
{
public:
typedef double ret_type;
typedef double par1_type;
typedef double par2_type;
};
template<typename T>
class Test
{
public:
typename TypeTbl<T>::ret_type compute(
typename TypeTbl<T>::par1_type x,
typename TypeTbl<T>::par2_type)
{
return x;
}
};
int main()
{
Test<Test1> t1;
cout << t1.compute(65,6.18) << endl;
}
这样的写法使得类数据类型再做了一次抽象
4.指针的模板特化
泛型——一个重要的特征就是约定代码中的复杂数据类型的基本特征
比如说指向Student类的对象的指针pStudent和指向Teacher类的对象的指针pTeacher,两个没有任何关系,这两个要相互转化,就需要借助void*,如void*pTemp=pStudent,pTemp=pTeacher,在这个过程中,pTemp是不含有pStudent和pTeacher的信息的,这样会出现类型不安全问题:指针天生不具备向外提供数据类型的能力。指针仅仅是一个4个字节存储着地址信息的变量,它没有办法约束类型
如果想要对类型约束,我们需要对指针进行模板特化,这样衍生出符合接口定义的统一类型型别的别名
示例代码:
#include <iostream>
using namespace std;
template <typename T>
class Iterator_1
{
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type& reference;
};
template <typename T>
class Iterator_2
{
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type& reference;
};
template<typename T>
struct Traits
{
};
template<typename T>
struct Traits<T*>
{
typedef T value_type;
typedef value_type* pointer;
typedef value_type& reference;
};
int main()
{
Iterator_1<int>::value_type t1= 100;
cout << t1 << endl;
Iterator_2<double>::value_type t2 = 100.5;
cout << t2 << endl;
Traits<double*>::value_type t3 = 100.09;
cout << t3 << endl;
}
这样的写法对类型进行了规范,用户传进去什么类型,这种别名传出来的就是什么类型,比如Iterator_1中传入了int,实际最后t1也为int,Traits中传入了double,t3就是double类型
通过Traits的数据类型信息,可以有效的规范出类型型别统一,从而避免了类型转化问题
Traits技术也是泛型的基石