您的位置:首页 > 编程语言 > C语言/C++

C++模板的特化

2013-08-21 09:09 225 查看
【转载】http://www.blogjava.net/bacoo/archive/2009/06/22/283480.html

对模板特化的理解:

特化整体上分为全特化和偏特化,这一点大家都没有什么置疑,但是细分它们各包括哪几种状态就很难界定了,而且很多权威的书上都不一致,管它呢,反正我们能会用各种特化就可以了。

下面就谈谈我个人对特化的划分和定义:

所谓特化,就是将泛型的东东搞得具体化一些,从字面上来解释,就是为已有的模板参数进行一些使其特殊化的指定,使得以前不受任何约束的模板参数,或受到特 定的修饰(例如const或者摇身一变成为了指针之类的东东,甚至是经过别的模板类包装之后的模板类型)或完全被指定了下来。

这是网上某个人的一些看法:

模板有两种特化,全特化和偏特化(局部特化)

模板函数只能全特化,没有偏特化(以后可能有)。

模板类是可以全特化和偏特化的。

全特化,就是模板中模板参数全被指定为确定的类型。

全特化也就是定义了一个全新的类型,全特化的类中的函数可以与模板类不一样。

偏特化,就是模板中的模板参数没有被全部确定,需要编译器在编译时进行确定。

在类型上加上const、&、*( cosnt int、int&、int*、等等)并没有产生新的类型。只是类型被修饰了。模板在编译时,可以得到这些修饰信息。

我个人也比较赞同这位仁兄的划分,全特化的标志就是产生出完全确定的东西,而不是还需要在编译期间去搜寻适合的特化实现,貌似在我的这种理解下,全特化的 东西不论是类还是函数都有这样的特点,template <>然后是完全和模板类型没有一点关系的类实现或者函数定义,如果你要说,都完全确定下来了,那还搞什么模板呀,直接定义不就完事了?但是很 多时候,我们既需要一个模板能应对各种情形,又需要它对于某个特定的类型(比如bool)有着特别的处理,这中情形下就是需要的了。

既然刚才提到了全特化的标志,那么再说说其他一些共性的东西:

一个特化的模板类的标志:在定义类实现时加上了<>,比如class A<int, T>;而在定义一个模板类的时候,class A后面是没有<>的

全特化的标志:template <>然后是完全和模板类型没有一点关系的类实现或者函数定义

偏特化的标志:template <typename T.....>,就是说还剩下点东西,不像全特化<>整得那么彻底

首先推荐两个不错的网址:
http://www.cnblogs.com/cutepig/archive/2009/02/12/1389479.html http://read.newbooks.com.cn/info/175115.html
先说类模板的特化吧:
谁都没的说的全特化:

// general version
template<class T>
class Compare
{
public:
static bool IsEqual(const T& lh, const T& rh)
{
return lh == rh;
}
};

// specialize for float
template<>
class Compare<float>
{
public:
static bool IsEqual(const float& lh, const float& rh)
{
return abs(lh - rh) < 10e-3;
}
};
谁都没的说的偏特化:
template<class T1, class T2>
class A
{
}

template<class T1>
class A<T1, int>
{
} 接下来的特化种类,到底划归到全特化还是偏特化,你自己看着办吧,不过大致就以下这些了,逃不出我们的手掌心了:
特化为引用,指针类型:

// specialize for T*
template<class T>
class Compare<T*>
{
public:
static bool IsEqual(const T* lh, const T* rh)
{
return Compare<T>::IsEqual(*lh, *rh);
}
};
特化为另外一个类模板:
// specialize for vector<T>
template<class T>
class Compare<vector<T> >
{
public:
static bool IsEqual(const vector<T>& lh, const vector<T>& rh)
{
if(lh.size() != rh.size()) return false;
else
{
for(int i = 0; i < lh.size(); ++i)
{
if(lh[i] != rh[i]) return false;
}
}
return true;
}
};

混合型的:

template<typename T1, typename T2>

class X {...}; 

template<typename T>

class X<vector<T>, int&> {...}; //至于这里怎么都把T2搞没了变成只依赖一个模板参数T了的问题,大家别着急,我来告诉你个本质的东西,把我这么三点就可以了:1.模板参数个数一 致;2.只要template <...>里面有东西不是<>,比如typename T,那么特化时就得用到T;3.不进行任何对模板参数的修饰也是不行的,比如template<typename T> class<T>{...},至少你也得搞个const T之类的吧,呵呵。下面是我搞出来的几种特殊情况,它们都是正确的:

template<typename T1, typename T2>

class X {};

template<typename T>

class X<vector<T>, T&> {};

template<typename T>

class X<vector<T>, int&> {};

template<>

class X<vector<double>, int&> {};

template<typename T1, typename T2, typename T3>

class X<map<T1,T2>, T3&> {};

最后,还有一种超级牛X的,在tr1里面用以实现function的,以前我都没见过还可以这么玩的:

template<typename T>

class Y;//这是在声明一个类模板,既然声明了,以后就得按这个规矩来,在我们之前的编程经验里,可以重复声明一个东西没问题,但是为同一个东东重复声明出不 同的东西就不可以了,因此你就不能再声明诸如template<typename T1, typename T2> class Y;这样的声明了;其实没有什么是不能声明的,既然我们可以声明变量,声明函数,声明类,那么当然我们也可以声明函数模板或者类模板的。

template<typename R, typename P1, typename P2>

class Y<R (P1, P2)> {...};//针对带两个参数,有返回值的函数类型特化,这里R (P1,P2)是定义了一种类型,该类型是一个隐式的函数指针,返回R,参数为P1和P2,这种对函数指针的定义完全等同于R (*)(P1,P2),但是前一种定义很不常见,大家一般是不会注意到这个地方的。

好了,说了不少关于类模板的特化了,下面再简要说说函数模板的特化:

函数模板的特化只能是全特化,而不能是偏特化,因此对于函数的特化就比较简单了,就是重新搞一遍就可以了,举几个例子如下:

template <class T>

T mymax(const T t1, const T t2)

{

   return t1 < t2 ? t2 : t1;

}

template <>

const char* mymax(const char* t1,const char* t2)

{

   return (strcmp(t1,t2) < 0) ? t2 : t1;

}

但是你不能这么搞:

template <>

bool mymax(const char* t1,const char* t2)

{

   return (strcmp(t1,t2) < 0);

}

其实对于mymax这个模板函数的定义而言,是用一个模板参数控制了三个地方,那么你在特化的时候,就也需要用一个特定的类型修改那三处相应的地方,如果你非要返回bool,那么你只能再定义一个函数模板了:

template <class T>

bool mymax(const T t1, const T t2)

{

   return t1 < t2 ? t2 : t1;

}

问题又来了,大家都知道函数重载是不关心返回值的,而只关心参数个数以及类型是否不一致,不一致就是重载,但是对于模板函数而言,这个规矩不再成立,因为 任何与模板相关的东西都只是个架子放在那里而已,只要它符合语法规则就可以了,这些架子只是在有人要调用它们时才会发挥效力,也就是说,在编译的时候会为 你搜寻合适的模板函数或者类,只要能找到就ok了,而且还要求是只找到一个,要是找到多个也不行,呵呵。

其实,对于函数而言,虽然不能偏特化,即不能再在函数名字后面像模板类一样搞个<typename T>出来,但是可以通过函数的重载(注意这里说的重载是指的模板重载,而不是普通意义的函数重载)变通的实现偏特化:

template <typename T1, typename T2>

bool mymax(T1 t1, T2 t2)

{

   return t1 < t2 ? t2 : t1;

}

template <typename T1>

bool mymax(T1 t1, int t2)

{

   return t1 < t2 ? t2 : t1;

}

再谈谈函数模板参数的推导,大致有以下几种方法,但是不管怎么推导,都必须得保证在调用函数前能确定模板函数的各个模板参数的类型。

template <typename T1, typename T2>

T2 fun(T1 arg1, int arg2)

{

    T2 t2;

    return t2;

}

对于上面这种比较特殊的模板函数,你不能通过传递参数来自动得到所有模板参数的类型,因此你必须显示的指定T1和T2的类型,有两种方法可以实现此目的:

int (*pfun)(double,int) = fun;//借用函数指针定义

cout<<pfun(12.2,11)<<endl;

cout<<fun<int,double>(11, 3.2)<<endl;//直接指定类型

如果上述模板函数改为:

template <typename T1, typename T2>

T2 fun(T1 arg1, T2 arg2)

{

    return arg2;

}

那么除了上述两种指定模板参数类型的方法之外,由于该模板函数参数的类型都可以借由其参数获得,因此我们省去指定模板参数这一步骤,而直接调用该模板函数:

fun(23, 2.3);

最后,再谈谈非类型模板参数的问题,在《C++ Template》的第四章有介绍。

template<typename T, int LEN> struct stack {...};

template<int margin> int add(int x){return x+margin;}

上面两个例子分别对应了类和函数两种情形,有人说非类型的模板参数存在得毫无价值,实则不然,因为我们可以借由一个确定的数值来产生一种新的类型或者新的 函数。对于上面两个例子,我觉得用非类型模板参数就很有意义,分别实现了让用户指定stack的大小以及指定需要增加的边际值,关于更多这方面的应用,大 家可以在今后的开发过程中逐步发掘,此外,还很有必要强调一下对非类型模板参数的限制,不能使用浮点数、class类型的对象和内部链接对象(例如字符串 常量"hello world!")作为实参;它们可以是常整数(包括枚举值)或者指向外部链接对象的指针。

对外部链接对象的指针举个例子:

template <char const* name>

class MyClass {...};

extern char const s[] = ”hello”;

MyClass<s> x;       //OK

好了,模板这块内容我先将这么多。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: