博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

C++模板的特化

Posted on 2011-06-27 17:07  李大嘴  阅读(14604)  评论(0编辑  收藏  举报

【转载】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


好了,模板这块内容我先将这么多。

又从网上搞到点好东东,也贴在这里吧:

类模板:

* 如果类模板中含有静态成员,那么用来实例化的每种类型,都会实例化这些静态成员。

* 两个靠在一起的模板尖括号( > ) 之间需要留个空格,否则,编译器将会认为是在使用operator>>,导致语法错误。

* 特化的实现可以和基本类模板的实现完全不同。

* 类模板可以为模板参数定义缺省值,称为缺省模板实参,并且他们还可以引用之前的模板参数。

* 成员函数模版不能被声明为虚函数。

* 类模板不能和另外一个实体共享一个名称。

eg:

 

1         int C;
2 
3         class C;    // ok,    
4 
5         int X;
6 
7         template < typename T >
8 
9         class X;    // error. 和变量X冲突

非类型模板参数:

在编译期或链接期可以确定的常值。这种参数的类型必须是下面的一种:

a> 整型或枚举

b> 指针类型( 普通对象的指针,函数指针,成员指针 )

c> 引用类型( 指向对象或者指向函数的引用 )

其他的类型目前都不允许作为非类型模板参数使用


今天又突然挖掘出来点好东东,贴在这里:
template <typename t>
void f(t t) {} //f1

template <>
void f(int t) {} //f2

void f(int t) {} //f3

void f(char t) {} //f4

f(3); //invoke f3
f('3'); //invoke f4

/**
caveat: f3 must be put after f2, or an error occurs: specialization of void f(T) [with T = int] after instantiation;
notes: the compiler will use f3 as the instantiation for f1, and use f2 as the specialization for f1;
rule: specialization must be before instantiation (*);

Above we have discuss the template function, and then we'll focus on member template function.

acronym: MTF(member template function);
Firstly, you should pay attention to the rule: the specialization of MTF must be the outside of the class, i.e., inline should not be allowed.

Secondly, specialization and instantiation still follow the rule (*). But you'd better put the instantiation outside of the class since specialization must be the outside of the class(the root cause is: if you wanna specialize a MTF, you should give compiler the defenition of the templcate firstly. But if you use the specialization as the inline method, the specialization will be anxious since you can put the defination of MTF outside of the class. As you know, the inline member function precedes the non-inline member function. So the compiler will chose the safest way to solve it, i.e., the specialization must be put outside of class declaration).
*/