traits指的是参数特征,policy指的是设计策略,下面,我们进行一个学习和讨论。
Fixed Traits(固定的特征):缺省的参数类型
在讨论为什么引入traits之前,我们先来看看如下代码会发生什么问题?
#ifndef ACCUM_HPP
#define ACCUM_HPP
template <typename T>
inline T accum(T const* beg,T const* end){
T total=T();
while(beg!=end){
total+=*beg;
++beg;
}
return total;
}
#endif
函数调用:
#include "accum.hpp"
#include <iostream>
using namespace std;
int main(){
int num[]={1,2,3,4,5};
cout<<"the average value of the integer values is"<<accum(&num[0],&num[5])/5<<"\n";
char name="templates";
int length=sizeof(name)-1;
cout<<"the average value of the characters in \""<<name<<"\" is"<<accum(&name[0],&name[length])/length<<"\n";
return 0;
}
下面让我们来看看运行结果:
the average value of the integer values is 3
the average value of the characters in “templates” is -5
有木有非常吃鲸呀,哈哈哈,这里为甚针对char类型竟然得到-5的运行结果,原因在于我们针对char类型进行实例化的,而char类型的范围是很小的,及时对于相对较小的数值进行求和也会出现越界的情况。显然,我们可以通过引入一个额外的模板参数AccT来解决这个问题,其中AccT描述了变量total的类型和返回类型,这样,我们调用的时候代码就变成了:
accum<int>(&name[0],&name[length]);
下面,我们就引入今天的主角,traits,不用这么复杂的函数调用,通过使调用accum()时的累加物类型T和存储累加和的类型同时也是返回结果的类型AT之间产生一份关联,可以自动让程序判断自动判断进行判断返回类型,这种方法是不是比较智能哈,这儿我们将返回类型AT称为T的一个trait(特征、特性)。下面我们看看这种方式调用的代码:
accumulation.hpp
template <typename T>
class AccumulationTraits;
template <>
class AccumulationTraits<char>{
public:
typedef int AccT;
};
template<>
class AccumulationTraits<short>{
public:
typedef int AccT;
};
template<>
class AccumulationTraits<int>{
public:
typedef long AccT;
};
template<>
class AccumulationTraits<unsigned int>{
public:
typedef unsigned long AccT;
};
template<>
class AccumulationTraits<float>{
public:
typedef double AccT;
};
accum.hpp
#ifndef ACCUM_HPP
#define ACCUM_HPP
#include "accumulationtraits.hpp"
template<typename T>
inline typename AccumulationTraits<T>::AccT accum(T const* beg,T const* end){
typedef typename AccumulationTraits<T>::AccT AccT;
AccT total=AccT();
while(beg!=end){
total+=*beg;
++beg;
}
return total;
}
#endif
运行结果:
the average value of the integer values is 3
the average value of the characters in "templates" is 108
Value Traits(数值式特征)
但在上面的代码中,我们调用:
AccT total=AccT();
...
return total;
这并不能保证能够产生一个零值,因为AccT可能更笨就没有default构造函数。针对这样的问题,我们仍旧利用traits方式解决:
accumtraits3.hpp
template <typename T>
class AccumulationTraits;
template<>
class AccumulationTraits<char>{
public:
typedef int AccT;
static AccT const zero=0;
};
template<>
class AccumulationTraits<short>{
public:
typedef int AccT;
static AccT const zero=0;
};
template<>
class AccumulationTraits<int>{
public:
typedef long AccT;
static AccT const zero=0;
};
accum3.hpp
#ifndef ACCUM_HPP
#define ACCUM_HPP
#include "accumtraits3.hpp"
template <typename T>
inline typename AccumulationTraits<T>::AccT accum(T const* beg,T const* end){
typedef typename AccumulationTraits<T>::AccT AccT;
AccT total=AccumulationTraits<T>::zero;
while(beg!=end){
total+=*beg;
++beg;
}
return total;
}
#endif
但是上述的这种定义static变量初始化方式有一个很严重的缺点:只有当变量是整数时或enum时候,C++才允许我们对它进行初始化动作,然而我们自定义的classes甚至浮点数类型都被排除在外,不得直接设定初值,因此如下的特化版本是错误的:
template<>
class AccumulationTraits<float>{
public:
typedef double AccT;
static double const zero=0.0;//错误,不是整数类型
};
一个简单的替代方案是:不要在class内部定义value trait:
template<>
class AccumulationTraits<float>{
public:
typedef double AccT;
static double const zero;
};
//在某个源码中,找寻如下句子
double const AccumulationTraits<float>::zero=0.0;
尽管这样也可以完成任务,但是对编译器来说更加不透明了,编译器处理头文件时候往往察觉不到定义式位于其他文件中。这种情况下编译器无法运用数值zero就是0.0这一事实,因此,我们选用替代方案:不要求以inlined成员函数传回一个整数值。
template <typename T>
class AccumulationTraits;
template<>
class AccumulationTraits<char>{
public:
typedef int AccT;
static AccT zero(){
return 0;
}
};
template<>
class AccumulationTraits<short>{
public:
typedef int AccT;
static AccT zero(){
return 0;
}
};
template<>
class AccumulationTraits<int>{
public:
typedef long AccT;
static AccT zero(){
return 0;
}
};
template<>
class AccumulationTraits<unsigned int>{
public:
typedef unsigned long AccT;
static AccT zero(){
return 0;
}
};
template<>
class AccumulationTraits<float>{
public:
typedef double AccT;
static AccT zero(){
return 0.0;
}
};
AccT total=AccumulationTraits<T>::zero();
这样,又方便又安全,是不是很棒的一种方式呢?
Parameterized Traits(参数式特征)
先前小结中accum()所使用traits被称为fixed(固定式),因为一旦decoupled trait(解耦用的特征萃取机制)定义完毕,你就不可以在算法中覆写它,即一旦定义了这个分离的trait,就不能在算法中对其进行该改写。不过某些情况下这样的覆写可能是我们想要的,例如我们碰巧知道某一套浮点数可以被安全的累计放进一个同型的浮点数变量中,覆写traits就又饿能使我们的开发更高效。
原则上,可以这样解决:添加一个带有默认值的template parameter,默认值由我们traits template决定,这样一来大部分用户可以忽略额外的template argument,而特殊需求的用户则可以覆写(override)预先设计的累计类型。此方案唯一让人不开森的是:function templates不能拥有预设的template arguments。
#ifndef ACCUM_HPP
#define ACCUM_HPP
#include "accumtraits4.hpp"
template <typename T,typename AT=AccumulationTraits<T> >
class Accum{
public:
static typename AT::AccT accum(T const* beg,T const* end){
typename AT::AccT total=AT::zero();
while(beg!=end){
total+=*beg;
++beg;
}
return total;
}
};
#endif
上述template的大多数用户可能从来不需要明确提供第二个template argument,因为更具第一个template argument就可以获得适当的默认值。
通常我们会导入一些便捷函数(convenience function)用以简化界面:
//第二个参数由模板的缺省实参提供
template <typename T>
inline typename AccumulationTraits<T>::AccT accum(T const* beg,T const* end){
return Accum<T>::accum(beg,end);
};
//第二个实参由Traits实参提供,替换缺省实参
template <typename Traits,typename T>
inline typename Traits::AccT accum(T const* beg,T const* end){
return Accum<T,Traits>::accum(beg,end);
}
Policies(策略)和Policy Classes(策略类别)
策略指的是我们上述中的累加方式,如果我们换成累乘或者其他什么类似的运算,这样的话,我们可以在模板中只需更换相应的策略方式即可。
sumpolicy1.hpp
#ifndef SUMPOLICY_HPP
#define SUMPOLICY_HPP
class SumPolicy{
public:
template <typename T1,typename T2>
static void accumulate(T1& total,T2 const& value){
total+=value;
}
};
#endif
accum6.hpp
#ifndef ACCUM_HPP
#define ACCUM_HPP
#include "accumtraits4.hpp"
#include "sumpolicy1.hpp"
template <typename T,typename Policy=SumPolicy,typename Traits=AccumulationTraits<T> >
class Accum{
public:
typedef typename Traits::AccT AccT;
static AccT accum(T const* beg,T const* end){
AccT total=Traits::zero();
while(beg!=end){
Policy::accumulate(total,*beg);
++beg;
}
return total;
}
};
#endif
当然,我们可以选择累乘方式,如下定义
#include "accum6.hpp"
#include <iostream>
class MultPolicy{
public:
template<typename T1,typename T2>
static void accumulate(T1& total,T2 const& value){
total+=value;
}
};
//现在,轻松换用累乘方式处理
int main(){
int num={1,2,3,4,5};
std::cout<<"the product of the integer value is "<<Accum<int,MultPolicy>::accum(&num[0],&num[5])<<"\n";
return 0;
}
Member Templates和Template Template parameters
ifndef SUMPOLICY_HPP
#define SUMPOLICY_HPP
template <typename T1,typename T2>
class SumPolicy{
public:
static void accumulate(T1& total,T2 const& value){
total+=value;
}
};
#endif
#ifndef ACCUM_HPP
#define ACCUM_HPP
#include "accumtraits.hpp"
#include "sumpolicy.hpp"
template <typename T,template <typename,typename> class Policy=SumPolicy,typename Traits=AccumulationTraits<T> >
class Accum{
public:
typedef typename Traits::AccT AccT;
static AccT accum(T const* beg,T const* end){
AccT total=Traits::zero();
while(beg!=end){
Policy<AccT,T>::accumulate(total,*beg);
++beg;
}
return total;
}
};
#endif
通过template template parameter来存取policy classes,主要的优点是:使得policy class更容易携带若干状态信息(亦即static成员变量)只需要利用一个取决于template parameters的型别即可。
联合多个Policies和Traits
traits和policy只是使得template parameters的数量减少了,并没有完全消除对多个template parameters的需求,那么我们应该怎样安排这些参数的顺序呢?
一个简单的策略是:根据其默认值被选用的可能性,以递增顺序安排参数顺序,通常这意味着traits参数跟在policy参数后面,因为policy参数更常被客户端代码所覆写。
以泛型迭代器(general iterators)进行累计
#ifndef ACCUM_HPP
#define ACCUM_HPP
template <typename Iter>
inline typename std::iterator_traits<Iter>::value_type accum(Iter start,Iter end){
typedef typename std::iterator_traits<Iter>::value_type VT;
VT total=VT();
while(start!=end){
total+=*start;
++start;
}
return total;
}
#endif