C++模板详解

一、模板具体化:

    函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如int或double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时也被称为通用编程。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型(parameterized types)。

  创建模板,关键字templatetypename是必需的,除非可以使用关键字class代替typename。另外,必须使用尖括号。如下程序所示:

template <typename T>
void swap(T &a, T &b)
{
    T temp;
    temp = a;
    a = b;
    b = temp;      
}

注意:模板并非函数定义,但使用了int的模板实例是函数定义。

(1)隐式实例化:

最初,编译器只能通过隐式实例化,来使用模板生成函数定义,这也是我们最常用的方法;如可以像下面这样使用上面定义的函数模板:

short a, b;
swap(a, b); // T 为 short 类型
int c, d;
swap(c, d); // T 为 int 类型

  

  使用上面的例子程序,我们可以交换两个同类型(int,double……)的值,当如果T为数组、指针或者结构,那么编写的模板函数就无法处理这些类型了,一种方案是重载C++运算符;另一种方案是,为特定类型提供具体化的模板定义,下面就介绍第二种方案:

(2)显式实例化:

 现在C++还允许显式实例化(explicit instrantiation)。这意味着可以直接命令编译器创建特定的实例,如swap<int>()。其语法是,声明所需的种类——用<>符号指示类型,并在声明前加上关键字template:

template void swap<int>(int, int); // explicit instrantiation

实现了这种特性的编译器看到上述声明后,将使用swap()模板生成一个使用int类型的实例。也就是说,该声明的意思是"使用swap()模板生成int类型的函数定义。"

  

(3)显式具体化:

 与显式实例化不同的是,显式具体化使用下面两个等价的声明之一:

template <> void swap<int> (int &, int &);     //explicit specialization
template <> void swap (int &, int &);     //explicit specialization

显式实例化和显式具体化区别在于:这些声明的意思是“不要使用swap()模板来生成函数定义,而应使用专门为int类型显式地定义的函数定义。”这些原型必须有自己的函数定义。显式具体化声明在关键字template后包含<>,而显式实例化没有

 警告:试图在同一个文件中(或转换单元)中使用同一种类型的显式实例化和显式具体化 将出错。

 隐式实例化、显式实例化和显式具体化统称为具体化(specialization)。它们的相同之处在于,它们表示的都是使用具体类型的函数定义,而不是通用描述。

引入显式实例化之后,必须使用新的语法——在声明中使用前缀template和template<>,以区分显式实例化和显式具体化。通常,功能越多,语法规则也越多。

下面的代码总结了这些概念:

……
struct job {};

template<typename T>
void swap(T &, T &);     // template prototype 模板

template<>
void swap<job>(job &, job &);     // explicit specialization for job 模板具体化

int main(void)
{
  template void swap<char>(char &, char &);          // explicit instrantiation for char 模板 显式实例化

  short a, b;
  ……
  swap(a, b);     //implicit template instantiation for short(启用一个模板) 
  job n, m;
  ……
  swap(n, m);  // use explicit specialization for job(模板具体化,不启用模板)
  char g, h;
  ……
  swap(g, h);  // use explicit template instantiation for char(启用另一个模板)
  ……

}

(4)部分具体化:

C++还允许部分具体化(partial speciazation),即部分限制模板的通用性。例如,部分具体化可以给类型参数之一指定具体的类型:

// general template
template <typename T1, typename T2> class Pair { …… };
// specialization with T2 set to int
template <typename T1> class Pair<T1, int> { …… }; 

关键字template后面的<>声明的是没有被具体化的类型参数。因此,上述第二个声明将T2具体化为int,但T1保持不变。注意,如果指定所有的类型,则<>内将为空,这将导致显式具体化:

// specialization with T1 and T2 set to int
template <> class Pair<int, int> { …… }; 

如果有多个模板可供选择,编译器将使用具体化程度最高的模板。

下面是模板部分具体化在luaTinker中的实际应用例子:

// ------------------------------------------------------------------------------
/*** 以下代码参考了 luaTinker 中 关于模板的使用 **/
#include <iostream> using namespace std; // "if_<bool, A, B>":类型选择模板(使用模板来实现,根据参数不同匹配不同的模板) template<bool C, typename A, typename B> struct if_ {}; // 模板具体化(相对于一般模板,有更高的优先调用级;如果和具体化模板匹配,则调用具体化模板) // 模板的部分具体化(模板的参数依然是三个,只不过第一个参数被默认指定而已) template<typename A, typename B> struct if_<true, A, B> {typedef A type;}; // first place template<typename A, typename B> struct if_<false, A, B> {typedef B type;}; // second place int main() { if_<true, char, double>::type bb; // 直接匹配 first place 位置部分具体化模板函数 cout << " the sizeof(bb) is = " << sizeof(bb) << endl; if_<false, char, double>::type cc; // 直接匹配 second place 位置部分具体化模板函数 cout << " the sizeof(cc) is = " << sizeof(cc) << endl; /*下面使用两个参数会报 参数不匹配 错误 * if_<char, int>::type dd; * cout << " the sizeof(dd) is = " << sizeof(dd) << endl; */ return 0; } // output ------------------------------------------------------------------------- the sizeof(bb) is = 1 the sizeof(cc) is = 8 请按任意键继续. . .

 二、编译器选择使用哪个函数版本:

对于函数重载、函数模板和函数模板重载,C++需要(且有)一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程称为重载解析(overloading resolution)。详细解释这个策略将需要将近一章的篇幅,因此这里我们只是大致了解一下这个过程是如何进行的。

(1)创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数。

(2)使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式的转换序列,其中包括实参类型与相应的形参类型完全匹配的情况。例如,使用float参数的函数调用可以将该参数转换为double,从而与double形参匹配,而模板可以为float生成一个实例。

(3)确定是否有最佳的可行函数。如果有,则使用它;否则,该函数调用出错。

  

三、tuple 和 可变参数模板(C++11新标准添加功能):

【这部分转自】http://www.cnblogs.com/hujian/archive/2012/02/23/2364190.html 

C++11中引入的tuple是一个N元组。它相当于有N个成员的结构体,只不过这个结构体的成员都是匿名的。tuple中有两个特殊的函数,一个是head(),用于获取第一个成员的值,另一个是tail(),用于获取剩下所有成员的值,tail()本身又是一个tuple。这样,如果我们想取tuple中第二个成员的值,则可以先取tail()的值,再取tail()的head()的值。当然,这样使用的话比较麻烦,所以C++ 11提供了get函数通过索引来获取tuple中某个成员的值。另外,通过make_tuple可以很方便地构造一个tuple对象。有关tuple使用的例子可以参考下面的代码。

tuple<int, char, string> tupInfo(10, 'A', "hello world");
int a = tupInfo.head();
int a2 = tupInfo.tail().head();
tuple<char, string> tupTail = tupInfo.tail();
int b = get<0>(tupInfo);
char c = get<1>(tupInfo);
string s = get<2>(tupInfo);
auto tupInfo2 = make_tuple(5, 'B', string("C++ 11"), 4.6);

前面说过,tuple是一个N元组,而N的个数是没有限制的,也就是说,tuple可以包含0个、1个、2个或更多的元素,每个元素的类型则通过模板参数指定。那么,tuple是如何做到这些的呢?答案是可变参数模板。学习C++的人应当对printf函数都非常熟悉,printf的一个特点就是它的参数个数是可变的。而在C++ 11中,则允许模板的参数个数也是可变的。下面是一个模板参数可变的函数模板,用于获取传入的参数的个数。

template<typename... Args>
UINT GetParameterCount(Args... args)
{
 return sizeof...(args);
}

可以看到,可变参数模板使用typename再加...来表示模板参数包使用Args再加...来表示函数参数包。上面代码中的sizeof...专门用于获取函数参数包中参数的个数,它的参数必须是一个函数参数包类型的对象。熟悉了可变参数模板的基本语法后,下面我们使用它来编写一个Print函数,该函数的参数个数和类型都是可变的,它简单地输出传入的各个参数的值,值之间用逗号进行分割,并在输出最后一个参数的值后自动换行。

template<typename T>
void Print(T value)
{
 cout << value << endl;
}

template<typename Head, typename... Rail>
void Print(Head head, Rail... rail)
{
 cout << head << ",";
 Print(rail...);   // 递归调用可变参数模板
}

int main(int argc, char *argv[])
{
 Print(1);                  // 输出:1
 Print(1, "hello");         // 输出:1,Hello
 Print(1, "hello", 'H');    // 输出:1,Hello,H
 return 0;
}

在上面的代码中,我们先定义了一个只有一个模板参数的函数模板,它简单地输出传入的参数的值。然后又定义了一个可变参数的函数模板,它输出第一个参数的值,然后递归地调用自己。注意rail...这种写法,它表示将函数参数包分割成一个一个的参数,并传入Print中。这样,函数参数包中的第一个参数传递给head,剩余的参数又重新构成一个函数参数包传递给rail。当递归调用到函数参数包中只有一个参数时,则会调用只有一个模板参数的Print函数

下图是对可变参数模板一个调用过程中参数传递变化的图解:

四、模板类与友元

 模板类声明也可以有友元。模板的友元分3类:

  非模板 友元。(类模板具体化:非模板友元 == n:1)

  约束模板 友元,即友元的类型取决于类被实例化时的类型。(类模板具体化:非模板友元 == 1:1)

  非约束模板 友元,即友元的所有具体化都是类的每一个具体化的友元。(类模板具体化:非模板友元 == 1:n)

(1)模板类的非模板友元函数(类模板具体化:非模板友元 == n:1)

在模板类中将一个常规函数声明为友元:

template <class T>
class HasFriend
{
 public:
     friend void counts();           // 
}

上述声明使counts()函数成为模板所有实例化的友元。例如,它将是类HasFriend<int>和hasFriend<string>的友元。

counts()函数不是通过对象调用的(它是友元,不是成员函数),也没有对象参数,那么它如何访问HasFriend对象呢?有很多种可能性。它可以访问全局对象;可以使用全局指针访问非全局对象;可以创建自己的对象;可以访问独立于对象的模板类的静态数据成员。

假设 要为友元函数提供 模板类参数,可以如下所示来进行友元声明吗?

friend void report(HasFriend &); 

答案是不可以。原因是不存在HasFriend这样的对象,而只有特定的具体化,如HasFriend<short>。要提供模板类参数,必须指明具体化。例如,可以这样做:

template <class T>
class HasFriend
{
    friend void report(HasFriend<T> &);  
    ……
};

(2) 模板类的约束模板友元函数(类模板具体化:非模板友元 == 1:1)

使友元函数本身成为模板,具体地说,为约束模板友元作准备,要使类的每一个具体化都获得与友元匹配的具体化。包含以下3步。

1. 在类定义的前面声明每个模板函数。

template<typename T> void counts();
template<typename T> void report(T &);

2. 在函数中再次将模板声明为友元。这些语句根据类模板参数的类型声明具体化(带<>尖括号):

template<typename TT>
class HasFriendT
{
    ……
    friend void counts<TT>();
    friend void report<>(HasFriendT<TT> &);
};

声明中的<>指出这是模板具体化。对于report(),<>可以为空,因为可以从函数参数推断出如下模板类型参数

HasFriendT<TT>

然而,也可以使用:

report<HasFriendT<TT> >(HasFriendT<TT> &)

但counts()函数没有参数,因此必须使用模板参数语法(<TT>)来指明其具体化还需要注意的是,TT是HasFriendT类的参数类型。

3. 程序必须满足的第三个要求是,为友元提供模板定义,也就是友元模板的实现函数体。

template<typename T>
void counts()
{
     cout << "template size :" << sizeof(HasFriendT<T>) << ";" ;
     cout << "template counts(): " << HasFriendT<T>::item << endl;
}

(3)模板类的非约束模板友元函数(类模板具体化:非模板友元 == 1:n)

前一节中的约束模板友元函数是在类外面声明的模板的具体化,int类具体化获得int函数具体化,依此类推。

通过在类内部声明模板,可以创建非约束模板友元函数,即每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的:

template<typename T>
class ManyFriend
{
   ……
   template <typename C, typename D> friend void show2(C &, D &);
};

下面的程序是一个使用非约束友元的例子。其中,函数定义show2(hfi1, hfi2)与下面的具体化匹配:

void show2<ManyFriend<int> &, ManyFriend<int> &>(ManyFriend<int> & c, ManyFriend<int> & d);

因为它是所有ManyFriend具体化的友元,所以能够访问所有具体化的item的成员,但它只访问了ManyFriend<int>对象。

同样,show2(hfd, hfi2)与下面具体化匹配:

void show2<ManyFriend<double> &, ManyFriend<int> &>(ManyFriend<double> & c, ManyFriend<int> & d);

它也是所有ManyFriend具体化的友元,并访问了ManyFriend<double>对象的item成员和ManyFriend<int>对象的item成员。

#include<iostream>
using std::cout;
using std::endl;

template<typename T>
class ManyFriend
{
private:
      T item;
pulbic:
      ManyFriend(const T & i) : item(i) {}
      template<typename C, typename D> void show2(C & c, D & d);
};

template <typename C, typename D> void show2(C & c, D & d)
{
    cout << c.item << " , " << d.item << endl;
}

int main()
{
     ManyFriend<int> hfi1(10);
     ManyFriend<int> hfi2(20);
     ManyFriend<double> hfdb(10.5);
     cout  << "hfi1, hfi2 : ";
     show2(hfi1, hfi2);
     cout << "hfdb, hfi2 : ";
     show2(hfdb, hfi2);

     return 0;
}


// output
hfi1, hfi2 : 10, 20
hfdb, hfi2 : 10.5, 20
 

 其他内容如:递归使用模板,模板参数,类模板,成员模板,模板别名等,以后待续……

参考:《C++ Primer Plus(第六版)》、《Effective C++》……

原文地址:https://www.cnblogs.com/yyxt/p/4256022.html