2.2 traits技术
STL 标准模板库非常强调软件的复用,traits 技术是重要的手段。
traits 就像特性萃取机,提取不同类的共性,以便能统一处理。
traits 依靠显式模板特殊化来把代码中因类型不同而发生变化的片段拖出来,用统一的接口来包装。这个接支)可以包含一个C++类所能包含的任何东西,如内类型、成员函数、成员变量。
作为客户的模板代码,可以通过traits模板类所公开的接口来间接访问。
例 已知整型数组类 CIntArray浮点数组类 CFloatArray求整型或浮点数的和乘以相应倍数并输出:
#include<iostream>
using namespace std;
//已知整型数组类 CIntArray,
//浮点数组类 CFloatArray,求整型或浮点数的和乘以相应倍数并输出。
class CIntArray{
int a[10];
public:
CIntArray(){
for (int i=0;i<10;i++){//这里是从1+到10
a[i]=i+1;}
}
int GetSum(int times)//times指整数的倍数{
int sum=0;
for(int i=0;i<10;i++) {
sum+=a[i];}
return sum* times;
}
};
class CFloatArray{
float f[10];
public:
CFloatArray() //a[0]=1,a[1]=1/2,.
{
for(int i=1;i<=10; i++){
f[i-1]=1.0f/i;}
}
float GetSum(float times)//浮点倍数
{
float sum=0.0f;
for(int i=0;i<10;i++) {
sum+=f[i];}
return sum * times;
}
};
int main()
{
CIntArray intary;
CFloatArray fltary;
cout<<"整型数组和3倍是:"<<intary.GetSum(3)<<endl;
cout<<"浮点数组和3.2倍是:"<<fltary.GetSum(3.2f)<<endl;
return 0;
}
上述代码把整型或浮点数组的和乘以相应倍数并输出。CIntArray.CFloatArray功能相似,main 函数中通过调用相应类(两个类)的 GetSum 函数完成。
下面是通过一个类的接口函数来完成上述功能(从int main之后修改成这样)
template<class T>
class CApply
{
public:
float Getsum(T& t,float inpara)
{
return t.GetSum(inpara);
}
};
int main()
{
CIntArray intary;
CFloatArray fltary;
CApply<CIntArray>c1;
CApply<CFloatArray>c2;
cout<<"整型数组和 3倍是:"<<c1.Getsum(intary,3)<<endl;
cout<<"浮点数组和 3.2倍是;"<<c2.Getsum(fltary,3.2f)<<endl;
}
通过模板类 CApply 接口函数实现了对整型数组及浮点数组类的操作 但是仔细观察发现 CIntArray类中 GetSum 函数返回值是整数,函数输人多数是整型;CFloatArray类中 GetSum 函数返回值是浮点类型,函数输入参数是浮点类型而模板类CApply 中 GetSum 把返回值固定成 loat,输入参数也固定成 foat。
当函数,类或者一些封装的通用算法中的某些部分会因为数据类型不同而导致处理或逻辑不同(而我们又不希望因为数据类型的差异而修改算法本身的封装时),traits会是一种很好的解决方案。
traits是服务于泛型编程的,其目的是让模板更加通用,同时把一些细节向普通的模板用户隐藏起来。下面是利用traits进行的代码。
步骤 1: 定义基本模板类---创建 NumTraits 模板类
这个模板类没有实际的实现,它只是用来为特定类型 T 提供类型别名。NumTraits 可以什么内容都不写,只是说明该类是一个模板类
template <class T>
class NumTraits { };
步骤 2: 模板特化---针对 CIntArray 类特化 NumTraits 类
为 CIntArray 类指定了两个类型别名 resulttype 和 inputpara,它们都被设定为 int 类型。
template <>
class NumTraits<CIntArray> {
public:
typedef int resulttype;
typedef int inputpara;
};
PS:看出相应模板特化类中只是用了 typedef 重定义函数。NumTraits<CIntArray>中,根据 CIntArray 类中函 int GetSum(int times),把返回值 int 类型重新定义成resulttype,把输人 int 类型重新定义成 inputpara。
把返回类型、输人参数都定义成相同的名称:为编制模板类共同的调用接口做准备。
下面同理是设定为float型
template <>
class NumTraits<CFloatArray> {
public:
typedef float resulttype;
typedef float inputpara;
};
步骤 3: 统一模板调用类编制---创建 CApply 类
使用 typename 关键字来指定依赖类型,这是 CApply 类的定义,它是一个模板类。使用 NumTraits<T>::resulttype 和 NumTraits<T>::inputpara 来定义类型别名 result 和 input。GetSum 函数接受一个类型为 T 的对象 obj 和一个类型为 input 的参数 in,并调用 obj.GetSum(in) 返回结果。
主函数中创建了 CIntArray 和 CFloatArray 的实例 intary 和 fltary,以及 CApply 类的实例 c1 和 c2。使用 c1.GetSum(intary, 3) 和 c2.GetSum(fltary, 3.2f) 分别调用 CApply 类的 GetSum 函数,并将结果打印到控制台
template <class T>
class CApply {
public:
typedef typename NumTraits<T>::resulttype result;
typedef typename NumTraits<T>::inputpara input;
result GetSum(T& obj, input in) {
return obj.GetSum(in);
}
};
int main() {
CIntArray intary;
CFloatArray fltary;
CApply<CIntArray> c1;
CApply<CFloatArray> c2;
cout << "整形数组和3倍是: " << c1.GetSum(intary, 3) << endl;
cout << "浮点数组和3.2倍是: " << c2.GetSum(fltary, 3.2f) << endl;
return 0;
}
ps:注意开始的时候这里使用 统一模板调用类编制。
template<class T>
class CApply
{
public:
NumTraits<T>::resulttype GetSum(Ts obj, NumTraits<T>::inputpara in)
{
return obj.GetSum(in);
}
};
乍一看起来,GetSum 函数有些难懂。当模板参数代表 CIntArray,该定义变为代码:
NumTraits<CIntArray>::resulttype GetSum(CIntArrays obj, NumTraits<CIntArray>::inputpara in)
根据(2)中模板特化定义
NumTraits<CIntArray>::resulttype 代表 int。
NumTraits<CIntArray>::inputpara代表int,
于是上式变为int GetSum(CIntArray& obj,int in)。
当模板参数代表 CFloatArray,该定义变为如下代码
Numlraits< CFloatArray>:: resulttype GetSum (CFloatArray6 obj, Numraits < CFloatArray>::inputpara in)
根据(2)中模板特化定义,
NumTraits<CFloatArray>:;resulttype 代表int,NumTraits<CFloatArray>::inutpara 代表 int
于是上式变为 float GetSum(CIntArray& obj,float in)
因此原函数NumTraits<T>::resulttype GetSum(T& obj,NumTraits<T>::inputpara in)中返回值及输人参数的类型是可变的。
随 NumTraits<T>::resulttypeNumTraits<T>::inputpara 化而变化。所以在模板特化类中输人、输出参数进typedef 重定义非常重要,而且起的对应名称还要相同。
为了简化 CApply 函数中定义形式,再次巧妙运用 typedef 进行定义。
template<class T>
class CApply
{
public:
typedef typename NumTraits< T> ::resulttype result;
typedef typename NumTraits<T> ::inputpara input;
result Getsum(T& obj, input in)
{
return obj.GetSum(in)
}
};
2.3 习题
1.实现traits技术的主要步骤有(全选)
A.定义基本模板类 B.模板特化 C.定义基本容器类 D.统一模板调用类编制
2.下列不是实现萃取技术的步骤为()
A. 定义基本模板类B.模板特化
C定义基本容器类D.统一模板调用类编制
A定义基本模板类:定义一个通用的模板类,用于提供默认的行为或属性
B模板特化:通过特化(partial specialization)或完全特化(full specialization)来为特定类型提供自定义的行为或属性。
D. 统一模板调用类编制:编写一个统一的模板调用类,通过使用类型萃取技术(type traits)来选择合适的实现或行为
3、traits技术依靠( 显示模板特殊化)来把代码中因类型不同二发生变化的片段拖出来,用统一的接口来包装
4.STL中主要使用了什么样的技术,模板特化起到什么样的作用?
STL中主要使用了traits技术,traits就像特性萃取剂,提取不同类的共性,以便能统一处理。Traits依靠显示模板特殊化来把代码中因类型不同而发生变化的片段拖出来,用统一的接口包装,这个接口可以包含一个C++类所能包含的任何东西,如内嵌类型,成员函数,成员变量。作为客户的模板代码,可以通过traits模板类所公开的接口来间接访问。
6.trsits技术在解决输入,输出参数类型不同时的步骤:
答案:(1)定义基本模板类(2)模板特化(3)统一模板调用类编制
7. 问题3:Traits技术**
template <typename T>
struct Traits {
static const bool isInteger = false;
};
template <>
struct Traits<int> {
static const bool isInteger = true;
};
int main() {
std::cout << std::boolalpha;
std::cout << Traits<float>::isInteger << std::endl;
std::cout << Traits<int>::isInteger << std::endl;
return 0;
}
上面的代码中,`Traits`是一个traits类,用于提供类型特性信息。请解释在`main`函数中,为什么`Traits<float>::isInteger`的结果是`false`,而`Traits<int>::isInteger`的结果是`true`。
Traits类通过特化为不同的类型提供了不同的特性信息。在通用的模板实现中,isInteger成员被默认设置为false。但是,当我们特化Traits<int>时,我们将isInteger成员特化为true,以表示int类型是整数类型。