【原】复习 Template

发布时间:2011-4-11 23:15
分类名称:STL


最近准备复习下STL,Boost之类的鸟玩意。所以从最基本的Template开始。

 

常见错误

template <class Type>

Type min1( Type a, Type b )

{

    // C++ Primer : 错误, 重新声明模板参数 Type

    // 但是我使用VS2010编译通过,只是给出了一个警告

    typedef double Type;

    Type tmp = a < b ? a : b;

    return tmp;

}

 

// Type 重复使用。 就如同参数名不能一样。一样了编译器就无法识别了。

// 所以判断一个语法是否正确的通法是:是否能够造成编译器无法分析

template <class Type, class Type>

Type min2( Type a, Type b);

 

// typename or class 关键字不能省略。

template <typename T, U>

T sum( T*, U );

 

// 有时,编译器无法区分出是类型以及不是类型的表达式

// 如果编译器在模板定义中遇到表达式Parm::name Parm 这个模板类型参数代表了一个类,

// 那么name引用的是Parm的一个类型成员吗?

// 编译器不知道name是否为一个类型, 因为它只有在模板被实例化之后才能找到Parm

// 表示的类的定义. 为了让编译器不为之混淆,你必须显示的告诉编译器它是否是个类型表达式,

// 使用typename可以做到,所以在我转的另一篇的typename的用法中提到,typename用在模板中,目的是为了帮助编译器区分它是个类型,

//  否则编译器默认将其看做一个成员对待。

template <class Parm, class U>

Parm minus( Parm* array, U value )

{

    Parm::name * p;          // 这是一个指针声明还是乘法乘法?

    typename Parm::name * p; // ok: 指针声明

}

 

// ok: 关键字跟在模板参数表之后

template <typename Type>

inline

Type min( Type, Type );

 

// 错误: inline 指示符放置的位置错误

inline

template <typename Type>

Type min( Array<Type>, int );


// 正确: inline 指示符放置的位置

template <typename Type>

inline Type min( Array<Type>, int );

 

 

函数模板实例化

这个过程是隐式发生的它可以被看作是函数模板调用或取函数模板的地址的副作用

在下面的程序中min()被实例化两次:一次是针对5 int 的数组类型,另一次是针对6 double 的数组类型。

 

template <typename Type, int size>

Type min( Type (&r_array)[size] )

 

// size 没有指定——ok

// size = 初始化表中的值的个数

int ia[] = { 10, 7, 14, 3, 25 };

double da[6] = { 10.2, 7.1, 14.5, 3.2, 25.0, 16.8 };

 

// 5 int 的数组实例化min()

// Type => int, size => 5

int i = min( ia );

 

// 6 double 的数组实例化 min()

// Type => double, size => 6

double d = min( da );

 

模板实参推演

为了判断用作模板实参的实际类型和值编译器需要检查函数调用中提供的函数实参的类型。在我们的例子中ia 的类型(即5 int 的数组)和da 的类型(即6 double 的数组)被用来决定每个实例的模板实参。用函数实参的类型来决定模板实参的类型和值的过程被称为模板实参推演template argument deduction)。

 

我们也可以不依赖模板实参推演过程,而是显式地指定模板实参。(需要调用者来明确)

 

函数模板在它被调用取其地址时被实例化。在下面的例子中指针pf 被函数模板实例的地址初始化,编译器通过检查pf 指向的函数的参数类型来决定模板实例的实参。

 

template <typename Type, int size>

Type min( Type (&p_array)[size] ) { /* ... */ }

// pf 指向 int min( int (&)[10] )

int (*pf)(int (&)[10]) = &min;

 

Type 的模板实参为int size 的模板实参为10 被实例化的函数是min(int(&)[10]),指针pf 指向这个模板实例。

 

在取函数模板实例的地址时,必须能够通过上下文环境为一个模板实参决定一个惟一的类型或值。如果不能决定出这个惟一的类型或值,就会产生编译时刻错误。

 

template <typename Type, int size>

Type min( Type (&r_array)[size] ) { /* ... */ }

typedef int (&rai)[10];

typedef double (&rad)[20];

void func( int (*)(rai) );

void func( double (*)(rad) );

int main() {

    // 错误: 哪一个 min() 的实例?

    func( &min );

}

 

因为函数func()被重载了,所以编译器不可能通过查看func()的参数类型,来为模板参数Type 决定惟一的类型,以及为size 的模板实参决定一个惟一值。这种情况,可以使用显示调用。

 

C++ Primer讲述了模板实参推演的过程,我不建议去记忆和学习。因为曾经看过。看过多少遍,基本上就忘过多少遍。本人比较笨,希望能理解。呵呵。因为你知道了其原理和不知道其原理其实没有多大区别 ,在实际运用当中,很少需要你去了解那么深刻。

 

显式指定explicitly specify

 

// min5( unsigned int, unsigned int ) 被实例化

min5< unsigned int >( ui, 1024 );

 

// T1 不出现在函数模板参数表中

// 问题:T1 的模板实参不能从函数实参中被推演出来

template <class T1, class T2, class T3>

T1 sum( T2, T3 );

 

// 错误: T1 不能被推演出来

ui_type loc1 = sum( ch, ui );

 

// ok: 模板实参被显式指定

// T1 T3 unsigned int, T2 char

ui_type loc2 = sum< ui_type, char, ui_type >( ch, ui );

 

在显式特化(explicit specification)中,我们只需列出不能被隐式推演的模板实参,如同缺省实参一样,我们只能省略尾部的实参例

 

// ok: T3 unsigned int

// T3 ui 的类型中推演出来

ui_type loc3 = sum< ui_type, char >( ch, ui );

 

// ok: T2 char, T3 unsigned int

// T2 T3 pf 的类型中推演出来

ui_type (*pf)( char, ui_type ) = &sum< ui_type >;

 

// 错误: 只能省略尾部的实参

ui_type loc4 = sum< ui_type, , ui_type >( ch, ui );

 

 

类模板

声明

template <class T>

class QueueItem;

 

template <class Type, int size>

class Buffer;

 

// 模板实例没有指定size的大小时,默认为1024

template <class Type, int size=1024>

class Buffer;

 

定义

template <class Type>

class QueueItem {

public:

    QueueItem( const Type & );

private:

    Type item;

    QueueItem *next;

};

在类模板定义中吗,类模板的名字(QueueItem)相当于是(QueueItem<Type>)的缩写。这种简写形式只能被用在类模板QueueItem 自己的定义中, 以及在类模板定义之外出现的成员定义中。

 

类模板实例化

从通用的类模板定义中生成类的过程被称为模板实例化template instantiation)。类模板定义指定了怎样根据一个或多个实际的类型或值的集合来构造单独的类。Queue的类模板定义被用作“Queue类的特定类型的实例”的自动生成模板。

Queue<int> qi;

一个针对int 型对象的Queue 类就被从通用类模板定义中创建出来。所以,是实例的类型越多,被编译器实例化出来的类就越多,你的程序也就越庞大。所以,程序大小有要求的,最好不要用模板

 

与函数模板实例的模板实参不同的是,根据类模板实例被使用的上下文环境,编译器无法推断出类模板实例的模板实参

Queue qs; // 错误: 哪一个模板实例?

 

只有当代码中使用了类模板的一个实例的名字,并且上下文环境要求必须存在类的定义时,这个类模板才被实例化。并不是每次使用一个类都要求知道该类的定义。 如:

class Matrix;

Matrix *pm; // ok: 不需要类 Matrix 的定义

void inverse( Matrix & ); // ok 也不需要

 

// Queue<int> 没有为其在 foo() 中的使用实例化

void foo( Queue<int> &qi )

{

    Queue<int> *pqi = &qi;

    // ...

}

 

所以声明一个类模板实例的指针和引用不会引起类模板被实例化

 

定义一个类类型的对象时需要该类的定义。例如在下面的例子中,obj1 的定义就是错的。这个对象的定义要求让编译器知道Matrix 的大小,以便为obj1 分配正确的内存数

class Matrix;

Matrix obj1; // 错误: Matrix 没有被定义

 

class Matrix { ... };

Matrix obj2; // ok

 

如果一个对象的类型是一个类模板的实例,那么当对象被定义时,类模板也被实例化

Queue<int> qi;  // Queue<int> 被实例化

 

如果一个指针或引用指向一个类模板实例,那么只有当检查这个指针或引用所指的那个对象时,类模板才会被实例化。

void foo( Queue<int> &qi )

{

    Queue<int> *pqi = &qi;

    // 因为成员函数被调用, 所以 Queue<int> 被实例化

    pqi->add( 255 );

    // ...

}

 

非类型参数的模板实参

绑定给非类型参数的表达式必须是一个常量表达,也就是在编译时刻能够被计算出来。

 

// 这么定义是没问题的

template <int *ptr> class BufPtr { ... };

 

// 但是如果这么实例化就是不对的,因为模板实参不能在编译时刻被计算出来

BufPtr< new int[24] > bp;

 

template <int size> Buf{ ... };

template <int *ptr> class BufPtr { ... };

int size_val = 1024;

const int c_size_val = 1024;

Buf< 1024 > buf0;               // ok

Buf< c_size_val > buf1;         // ok

Buf< sizeof(size_val) > buf2;   // ok: sizeof(int)

BufPtr< &size_val > bp0;        // ok

 

// 错误:不能在编译时刻计算出来。

Buf< size_val > buf3;

 

类模板的成员函数

// 内部定义

template <class Type>

class Queue {

public:

    // inline 构造成员函数

    Queue() : front( 0 ), back ( 0 ) { }

    // ...

};

 

// 外部定义

template <class Type>

class Queue {

public:

    Queue( );

private:

    // ...

};

template <class Type>

inline Queue<Type>::

    Queue( ) { front = back = 0; }

 

类模板的成员函数本身也是一个模板。标准C++要求这样的成员函数只有在被调用或者取地址时,才被实例化(标准C++之前的有些编译器在实例化类模板时,就实例化类模板的成员函数。)用来实例化成员函数的类型就是其成员函数要调用的那个类对象的类型。

 

Queue<string> qs;

对象qs 的类型是Queue<string> 。当初始化这个类对象时,Queue<string>类的构造函数被调用,在这种情况下用来实例化构造函数的模板实参是string

 

常见错误

// 错误: 必须是 <typename T, class U> <typename T, typename U>

template <typename T, U>

    class collection;

 

在下面的例子中,item 的类型不是double,它的类型是模板参数的类型:

typedef double Type;

template <class Type>

class QueueItem {

public:

    // ...

private:

    // item 不是 double 类型

    Type item;

    QueueItem *next;

};

 

// 错误: 重复使用名为Type 的模板参数

template <class Type, class Type>

    class container;

 

template <class Type>

class Queue {

public:

    // ...

private:

// 错误: QueueItem 不是一个已知类型

// 应该为QueueItem<Type>, 或者确定类型: QueueItem<int>之类的

    QueueItem *front;

};

 

下面例子和书上说的有些分歧。

template <class Type>

class QueueItem {

public:

    // ...

private:

// C++ Primer:错误: 成员名不能与模板参数 Type 同名

// 但我使用VS2008VS2010,VS2012都可以编译通过,而且没有任何警告。

    typedef double Type;

    Type item;

    QueueItem *next;

};

 

// C++ Primer: ok: 考虑两个声明中的缺省实参

// 但我使用VS2008VS2010,VS2012无法编译通过,编译器要求size也得需要默认值。

template <class Type = string , int size>

    class Buffer;