《C++Primer》模板与泛型编程

● 定义函数模板

template <typename T>

int compare(const T &v1, const T &v2)

{

       if(v1 < v2) return –1;

       if(v2 < v1) return 1;

       return 0;

}

模板定义以关键字template开始,后接模板形参表,模板形参表是尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。模板形参表不能为空。

 

inline函数模板

函数模板可以用与非模板函数一样的方式声明为inline。说明符放在模板形参表之后、返回类型之前,不能放在关键字template之前。

template <typename T> inline T min(const T&, const T&);

 

● 定义类模板

template <class Type> class Queue{

public:

          Queue();

          Type &front();

           const Type &front () const;

          void push (const Type &);

          void pop();

          bool empty() const;

private:

          //…

}

 

● 模板形参

1.作用域:模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字。

2.用作模板形参的名字不能再模板内部重用。

3.对于模板可以只声明而不定义,每个模板类型形参前面必须带上关键字class或typename,每个非类型形参前面必须带上类型名字,省略关键字或类型说明符是错误的。

 

● 模板类型形参

类型形参由关键字class或typename后接说明符构成。

在函数模板形参中,关键词typename和class具有相同含义,可以互换使用,两个关键词都可以在同一模板形参表中使用。

通过在成员名前加上关键字typename作为前缀,可以告诉编译器将成员当做类型。

 

● 非类型模板形参

模板形参不必都是类型,如下函数模板声明了array_int是一个含有一个类型模板形参和一个非类型模板形参的函数模板。函数本身接受一个形参,该形参是数组的引用。

template <class T, size_t N> void array_init(T (&parm)[N])

{

   for(size_t i = 0; i != N; ++i)   parm[i] = 0;

}

当调用array_int时,编译器从数组实参计算非类型形参的值:

int x[42];

double y[10];

array_int(x);   //instantiates array_int(int(&)[42])

array_int(y);   //instantiates array_int(double(&)[10])

 

● 模板实参推断

当函数模板被调用时,对函数实参类型的检查决定了模板实参的类型和值的这个过程叫做模板实参推断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
using namespace std;
template<class T>void h(T a){cout<<" h()"<<typeid(T).name()<<endl;}  //带有一个类型形参T的模板函数的定义方法,typeid(变量名).name()为测试变量类型的语句。
template<class T>void k(T a,T b){T c;cout<<" k()"<<typeid(T).name()<<endl;} //注意语句T c。模板类型形参T可以用来声明变量,作为函数的反回类型,函数形参等凡是类类型能使用的地方。
template<class T1,class T2> void f(T1 a, T2 b){cout<<" f()"<<typeid(T1).name()<<","<<typeid(T2).name()<<endl;}   //定义带有两个类型形参T1,T2的模板函数的方法template<class T> void g(const T* a){T b;cout<<" g()"<<typeid(b).name()<<endl;}
//template<class T1,class T2=int> void g(){}  //错误,默认模板类型形参不能用于函数模板,只能用于类模板上。
//main函数开始
int main()
{ // template<class T>void h(){} //错误,模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行。
//函数模板实参推演示例。
// h(int); //错误,对于函数模板而言不存在h(int,int)这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行h(2,3)这样的调用,或者int a, b; h(a,b)。
//h函数形式为:template<class T>void h(T a)
h(2);//输出" h() int"使用函数模板推演,在这里数值2为int型,所以把类型形参T推演为int型。
h(2.0);//输出" h() double",因为2.0为double型,所以将函数模板的类型形参推演为double型
//k函数形式为:template<class T>void k(T a,T b)
k(2,3);//输出" k() int"
//k(2,3.0);错误,模板形参T的类型不明确,因为k()函数第一个参数类型为int,第二个为double型,两个形参类型不一致。
//f函数的形式为:template<class T1,class T2> void f(T1 a, T2 b)
f(3,4.0);//输出" f() int,double",这里不存在模板形参推演错误的问题,因为模板函数有两个类型形参T1和T2。在这里将T1推演为int,将T2推演为double。
int a=3;double b=4;
f(a,b); //输出同上,这里用变量名实现推板实参的推演。
//模板函数推演允许的转换示例,g函数的形式为template<class T> void g(const T* a)
int a1[2]={1,2};g(a1); //输出" g() int",数组的地址和形参const T*不完全匹配,所以将a1的地址T &转换为const T*,而a1是int型的,所以最后T推演为int。
g(&b); //输出" g() double",这里和上面的一样,只是把类型T转换为double型。
h(&b); //输出" h() double *"这里把模参类型T推演为double *类型。
return 0;
}

 

● 显式模板实参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std;
template<class T>void g1(T a, T b){cout<<"hansu g1()"<<typeid(T).name()<<endl;}
template<class T1,class T2,class T3>T1 g2(T2 a,T3 b)
{T1 c=a;cout<<"hansug2()"<<typeid(T1).name()<<typeid(T2).name()<<typeid(T3).name()<<endl; return c;}
template<class T1,class T2> void g3 ( T1 a ) {cout<<"hansu g3()"<<typeid(T1).name()<<typeid(T2).name()<<endl;}
template<class T1,int a> void g4(T1 b, double c){cout<<"hansu g4()"<<typeid(T1).name()<<typeid(a).name()<<endl;}
template<class T1,class T2> class A{public:void g();};
//模板显示实例化示例。
//因为模板的声明或定义不能在局部范围或函数内进行。所以模板实例化都应在全局范围内进行。
template void g1<double>(double a,double b); //把函数模板显示实例化为int型。
template class A<double,double>; //显示实例化类模板,注意后面没有对象名,也没有{}大括号。
//template class A<int,int>{};  //错误,显示实例化类模板后面不能有大括号{}。
//template class A<int,int> m;  //错误,显示实例化类模板后面不能有对象名。
//main函数开始
int main()
{//显示模板实参示例。显示模板实参适合于函数模板
//1、显示模板实参用于同一个模板形参的类型不一致的情况。函数g1形式为template<class T>void g1(T a, T b)
g1<double>(2,3.2);//输出"hansu g1() int"两个实参类型不一致,第一个为int第二个为double。但这里用显示模板实参把类型形参T指定为double,所以第一个int型的实参数值2被转换为double类型。
//g1(2,3.2);错误,这里没有用显式模板实参。所以两个实参类型不一致。
//2、用于函数模板的反回类型中。函数g2形式为template<class T1,class T2,class T3> T1 g2(T2 a,T3 b)
//g2(2,3);错误,无法推演类型形参T1。
//int g2(2,3);错误,不能以这种方法试图推导类型形参T1为int型。
//int a=g2(2,3);错误,以这种方式试图推演出T1的类型为int也是错误的。
g2<int,int,int>(2,3);//正确,将T1,T2,T3 显示指定为int型。输出"hansu g2() intintint"
//3、应用于模板函数的参数中没有出现模板形参的情况其中包括省略的用法,函数g3的形式为template<class T1,class T2> void g3(T1 a)
//g3(2);错误,无法为函数模板的类型形参T2推演出正确的类型
//g3(2,3);错误,岂图以这种方式为T2指定int型是错误的,因为函数只有一个参数。
//g3<,int>(2);错误,这里起图用数值2来推演出T1为int型,而省略掉第一个的显示模板实参,这种方法是错误的。在用显示模板实参时,只能省略掉尾部的实参。
//g3<int>(2);错误,虽然用了显示模板实参方法,省略掉了尾部的实参,但该方法只是把T1指定为int型,仍然无法为T2推演正确的类型。
g3<int,int>(2);//正确,显示指定T1和T2的类型都为int型。
//4、用于函数模板的非类型形参。g4函数的形式为template<class T1,int a> void g4(T1 b,double c)
//g4(3,3.2);错误,虽然指定了两个参数,但是这里仍然无法为函数模板的非类型形参int a推演出正确的实参。因为第二个函数参数x.2是传递给函数的参数double c的,而不是函数模板的非类型形参int a。
//g4(3,2);错误,起图以整型值把实参传递给函数模板的非类型形参是不行的,这里数值2会传递给函数形参double c并把int型转换为double型。所以非类型形参int a仍然无实参。
//int d=1; g4<int ,d >(3,3.2); //错误,调用方法正确,但对于非类型形参要求实参是一个常量表达式,而局部变量c是非常量表达式,不能做为非类型形参的实参,所以错误。
g4<int,1>(2,3.2);//正确,用显示模板实参,把函数模板的类型形参T1设为int型,把数值1传给非类型形参int a,并把a设为1,把数值2 传给函数的第一个形参T1 b并把b设为2,数值?.2传给函数的第二个形参double c并把c设为?.2。
const int d=1; g4<int,d>(2,3.2);//正确,这里变量d是const常量,能作为非类型形参的实参,这里参数的传递方法同上面的语句。
return 0;
}

 

● 模板类中的友元

1.非模板函数、类成为所有实例类的友元。

template <class Type> class Bar{

      friend   class  FooBar;

      friend   void fcn();

};

这个声明是说,FooBar的成员和fcn函数可以访问Bar类的任意实例的private成员和protected成员。

2.模板函数、模板类成为同类型实例类的友元。

template <class Type> class Bar{

    template <class T> friend class Foo1;

    template <class T> friend void temp1_fun1(const T&);

   //..

};

这个友元声明在Bar与其友元Foo1和temp1_fcn1的每个实例之间建立了一对多的映射。对Bar的每个实例而言,Foo1或temp1_fcn1的所有实例都是友元。

3.类也可以只授予对特定实例的访问权。

template <class T> class Foo2;

template <class T> void temp1_fcn2(const T&);

template <class Type> class Bar{

        friend  class Foo2<char*>;

        friend void temp1_fcn2<char*>(char* const &);

};

 

● 类模板的static成员

类模板可以像其他类一样声明static成员。

template <class T> class Foo

{

public:

        static std::size_t count() {return ctr}

private:

        static std::size_t ctr;

};

Foo类的每个实例化有自己的static成员:

//each object shares the same Foo<int>::ctr and Foo<int>::count members

Foo<int> fi,fi2,fi3;

//has static members Foo<string>::ctr and Foo<string>::count

Foo<string> fs;

 

Foo<int>::count();

 

● 模板特化

一是特化为绝对类型; 二是特化为引用,指针类型;三是特化为另外一个类模板。

这里用一个简单的例子来说明这三种情况:

1
2
3
4
5
6
7
8
9
10
// general version
template<class T>
class Compare
{
public:
    static bool IsEqual(const T& lh, const T& rh)
    {
        return lh == rh;
    }
};

这是一个用于比较的类模板,里面可以有多种用于比较的函数, 以IsEqual为例。

一、特化为绝对类型
也就是说直接为某个特定类型做特化,这是我们最常见的一种特化方式, 如特化为float, double等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// specialize for float
template<>
class Compare<float>
{
public:
    static bool IsEqual(const float& lh, const float& rh)
    {
        return abs(lh - rh) < 10e-3;
    }
};

// specialize for double
template<>
class Compare<double>
{
public:
    static bool IsEqual(const double& lh, const double& rh)
    {
        return abs(lh - rh) < 10e-6;
    }
};

二、特化为引用,指针类型
这种特化我最初是在stl源码的的iterator_traits特化中发现的, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template <class _Iterator>
struct iterator_traits {
  typedef typename _Iterator::iterator_category iterator_category;
  typedef typename _Iterator::value_type        value_type;
  typedef typename _Iterator::difference_type   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};

// specialize for _Tp*
template <class _Tp>
struct iterator_traits<_Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef _Tp*                        pointer;
  typedef _Tp&                        reference;
};

// specialize for const _Tp*
template <class _Tp>
struct iterator_traits<const _Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const _Tp*                  pointer;
  typedef const _Tp&                  reference;
};

当然,除了T*, 我们也可以将T特化为 const T*, T&, const T&等,以下还是以T*为例:

1
2
3
4
5
6
7
8
9
10
// 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);
    }
};

这种特化其实是就不是一种绝对的特化, 它只是对类型做了某些限定,但仍然保留了其一定的模板性,这种特化给我们提供了极大的方便, 如这里, 我们就不需要对int*, float*, double*等等类型分别做特化了。

三、特化为另外一个类模板

这其实是第二种方式的扩展,其实也是对类型做了某种限定,而不是绝对化为某个具体类型,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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;
    }
};

这就把IsEqual的参数限定为一种vector类型, 但具体是vector<int>还是vector<float>, 我们可以不关心, 因为对于这两种类型,我们的处理方式是一样的,我们可以把这种方式称为“半特化”。

当然, 我们可以将其“半特化”为任何我们自定义的模板类类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// specialize for any template class type
template <class T1>
struct SpecializedType
{
    T1 x1;
    T1 x2;
};
template <class T>
class Compare<SpecializedType<T> >
{
public:
    static bool IsEqual(const SpecializedType<T>& lh, const SpecializedType<T>& rh)
    {
        return Compare<T>::IsEqual(lh.x1 + lh.x2, rh.x1 + rh.x2);
    }
};

这就是三种类型的模板特化, 我们可以这么使用这个Compare类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 // int
    int i1 = 10;
    int i2 = 10;
    bool r1 = Compare<int>::IsEqual(i1, i2);

    // float
    float f1 = 10;
    float f2 = 10;
    bool r2 = Compare<float>::IsEqual(f1, f2);

    // double
    double d1 = 10;
    double d2 = 10;
    bool r3 = Compare<double>::IsEqual(d1, d2);

    // pointer
    int* p1 = &i1;
    int* p2 = &i2;
    bool r4 = Compare<int*>::IsEqual(p1, p2);

    // vector<T>
    vector<int> v1;
    v1.push_back(1);
    v1.push_back(2);

    vector<int> v2;
    v2.push_back(1);
    v2.push_back(2);
    bool r5 = Compare<vector<int> >::IsEqual(v1, v2);

    // custom template class
    SpecializedType<float> s1 = {10.1f,10.2f};
    SpecializedType<float> s2 = {10.3f,10.0f};
    bool r6 = Compare<SpecializedType<float> >::IsEqual(s1, s2);

 

更加详细的关于C++模版深度解析见:http://www.cnblogs.com/L-hq815/archive/2012/08/01/2619135.html

本文链接:http://www.alonemonkey.com/cplus-review-ten.html