C++(33)模板与泛型之实例化

来源:互联网 发布:84cs域名升级访问升级 编辑:程序博客网 时间:2024/06/05 22:38

模板与泛型编程

--实例化



引言:

模板是一个蓝图,它本身不是类或函数。编译器使用模板产生指定的类或函数的特定版本。产生模板的特定类型实例的过程称为实例化。

模板在使用时将进行实例化,类模板在引用实际模板类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。



1、类的实例化

当编写Queue<int>qi时,编译器自动创建名为Queue<int>的类。实际上,编译器通过重新编写Queue模板,用类型int代替模板形参的每次出现而创建Queue<int>类。实例化的类就像已经编写的一样:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <typename Type> class Queue<Type>  
  2. {  
  3. public:  
  4.     Queue();  
  5.     int &front();  
  6.     const int &front() const;  
  7.     void push(const int &);  
  8.     void pop();  
  9.     bool empty() const;  
  10.   
  11. private:  
  12.     //...  
  13. };  

如果要为string类型的对象创建Queue类,可以编写:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Queue<string> qs;  

在这个例子中,用string代替Type的每次出现。

【重点理解:】

类模板的每次实例化都会产生一个独立的类类型:为int类型实例化的Queue与任意其他Queue类型没有关系,对其他Queue类型的成员也没有特殊访问权限!



2、类模板实参是必需的

想要使用类模板,就必须显式指定模板实参:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Queue qs;   //Error  

类模板不定义类型只有特定的实例才定义了类型。特定的实例是通过提供模板实参每个模板形参匹配而定义的。模板实参在用逗号分隔并用尖括号括住的列表中指定:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Queue<int> qi;  
  2. Queue<string> qs;  

用模板类定义的类型总是包含模板实参。例如,Queue不是类型,而Queue<int>Queue<string>是类型



3、函数模板实例化

使用函数模板时,编译器通常会为我们推断模板实参:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int main()  
  2. {  
  3.     compare(1, 0);  //将模板形参替换为int  
  4.     compare(3.14, 2.7); //将模板形参替换为double  
  5. }  

这个程序实例化了compare的两个版本:一个用int代替T,另一个用double代替T,实质上是编译器为我们编写了compare的这两个实例:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int compare(const int &v1, const int &v2)  
  2. {  
  3.     if (v1 < v2)  
  4.         return -1;  
  5.     if (v2 < v1)  
  6.         return 1;  
  7.     return 0;  
  8. }  
  9. int compare(const double &v1, const double &v2)  
  10. {  
  11.     if (v1 < v2)  
  12.         return -1;  
  13.     if (v2 < v1)  
  14.         return 1;  
  15.     return 0;  
  16. }  

一、模板实参推断

要确定应该实例化哪个函数,编译器会查看每个实参。如果相应形参声明为类型形参的类型,则编译器从实参的类型推断形参的类型。从函数实参确定模板实参类型的过程叫做模板实参推断



1、多个类型形参的实参必须完全匹配

模板类型形参可以用作一个以上函数形参的类型。在这种情况下,模板类型推断必须为每个对应的函数实参产生相同的模板实参类型

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <typename T>  
  2. int compare(const T &val1,const T &val2)  
  3. {  
  4.     if (val1 < val2)  
  5.         return -1;  
  6.     else if (val2 < val2)  
  7.         return 1;  
  8.     return 0;  
  9. }  
  10.   
  11. int main()  
  12. {  
  13.     short si;  
  14.     compare(si,1024);   //Error:调用compare的实参类型不同,无法完成正确推断  
  15. }  

如果compare的设计者想要允许实参的常规转换,则函数必须用两个类型形参来定义

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <typename T,typename U>  
  2. int compare(const T &val1,const U &val2)  
  3. {  
  4.     if (val1 < val2)  
  5.         return -1;  
  6.     else if (val2 < val2)  
  7.         return 1;  
  8.     return 0;  
  9. }  
  10.   
  11. int main()  
  12. {  
  13.     short si;  
  14.     compare(si,1024);   //OK  
  15. }  

2、类型形参的实参的受限转换

一般而言,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行两种转换:

1const转换:接受const引用const指针的函数可以分别用const对象的引用指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略const,,无论传递const或非 const对象给接受非引用类型的函数,都使用相同的实例化。

2)数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <typename T>  
  2. T fobj(T,T);  
  3. template <typename T>  
  4. T fref(const T &,const T &);  
  5.   
  6. string s1("a value");  
  7. const string s2("another value");  
  8.   
  9. fobj(s1,s2);    //OK:call fobj(string,string),实参被复制  
  10. fref(s1,s2);    //OK:cal fref(const string &,const string &)  
  11.   
  12. int a[10],b[42];  
  13. fobj(a,b);  //OK:call fobj(int *,int *)  
  14. fref(a,b);  //Error:实参没有办法转换成为pointer[指针]  

3、应用于非模板实参的常规转换

注意下面的一段程序:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <typename Type>  
  2. Type sum(const Type &op1,int op2)  
  3. {  
  4.     return op1 + op2;  
  5. }  

因为op2的类型是固定的,在调用sum的时候,可以对传递给op2的实参应用常规转换:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. double d = 3.14;  
  2. string s1("hiya"),s2("world");  
  3. cout << sum(1024,d) << endl;  
  4. cout << sum(1.4,d) << endl;  
  5. cout << sum(s1,s2) << endl; //Error:没有从string到int的转换  

4、模板实参推断与函数指针

可以使用函数模板函数指针进行初始化或赋值,此时,编译器使用指针的类型实例化具有适当模板实参的模板版本。

例如,假定有一个函数指针指向返回int值的函数,该函数接受两个形参,都是 constint 引用,可以用该指针指向compare的实例化

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <typename T>  
  2. int compare(const T &,const T &);  
  3. int (*pf1)(const int &,const int &) = compare;  

pf1的类型是一个指针,指向“接受两个constint & 类型形参并返回int值的函数”,形参的类型决定了T的模板实参的类型T的模板实参为int,指针pf1引用的是将 T绑定到 int的实例化。

获取函数模板实例化的地址的时候,上下文必须是这样的:它允许为每个模板形参确定唯一的类型或值。

如果不能从函数指针类型确定模板实参,就会出错:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void func(int (*)(const string &,const string &));  
  2. void func(int (*)(const int &,const int &));  
  3. func(compare);  //Error:通过查看func的形参类型不可能确定模板实参的唯一类型。  

func的调用可以实例化下列函数中的任意一个:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. compare(const string&, const string&)   
  2. compare(const int&, const int&)  

因为不能为传给func的实参确定唯一的实例化,该调用会产生一个编译时(或链接时)错误!

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P540 习题16.22  
  2. template <class Type>  
  3. Type calc(const Type *arr,int size);  
  4.   
  5. template <class Type>  
  6. Type fcn(Type p1,Type p2);  
  7.   
  8. int main()  
  9. {  
  10.     double dobj;  
  11.     float fobj;  
  12.     char cobj;  
  13.     int ai[5] = {511,16,8,64,343};  
  14.   
  15.     calc(&cobj,'c');  
  16.     calc(&dobj,fobj);  
  17.     fcn(ai,ai + sizeof(ai)/sizeof(*ai));  
  18. }  


模板与泛型编程

--实例化[]



二、函数模板的显式实参

在某些情况下,不可能推断模板实参的类型。当函数的返回类型必须与形参表中所用的所有类型都不同时,最常出现这一问题。在这种情况下,有必要覆盖模板实参推断机制,显式指定为模板形参所用的类型或值



1、指定显式模板实参

如果函数形参类型不统一,该如何指定sum的返回类型?

[cpp] view plaincopy
  1. template <class T, class U>   
  2. ??? sum(T, U);   

此时使用任一形参调用都一定会在某些时候失败:

[cpp] view plaincopy
  1. sum(3, 4L); // 第二个类型较大,则应使用 U sum(T, U)  
  2. sum(3L, 4); // 第一个类型较大,则应使用 T sum(T, U)  

可以强制sum的调用者将较小的类型强制转换为希望作为结果使用的类型来解决此问题:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int i;  
  2. short s;  
  3. sum(static_cast<int>(s), i);  

2、在返回类型中使用类型形参

指定返回类型的一种方式是引入第三个模板形参,它必须由调用者显式指定:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <class T1,class T2,class T3>  
  2. T1 sum(T2,T3);  

有一个问题:没有实参的类型可用于推断T1的类型,相反,调用者必须在每次调用sum为该类型显式提供实参

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int i;  
  2. long lng;  
  3. long val = sum<long>(i,lng);  

这一调用显式指定T1的类型,编译器从调用中传递的实参推断T2T3的类型。

【小结】

显式模板实参从左至右与对应模板形参相匹配,假如可以从函数形参推断,则只有结尾(最右边)形参的显式模板实参可以忽略。

如果这样编写sum函数:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <class T1,class T2,class T3>  
  2. T3 alternative_sum(T2,T1);  

则总是必须为所有三个形参指定实参:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //没法识别模板实参的初始化  
  2. long val = alternative_sum<long>(i,lng);    //Error  
  3. long val2 = alternative_sum<long,int,long>(i,lng);  //OK  

3、显式实参与函数模板指针

可以使用显式模板实参的另一个例子是第16.2.1节中有二义性程序,通过使用显式模板实参能够消除二义性:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <typename T>  
  2. int compare(const T &,const T &);  
  3.   
  4. void func(int (*)(const string &,const string &));  
  5. void func(int (*)(const int &,const int &));  
  6.   
  7. func(compare<string>);  

像前面一样,需要在调用中传递compare实例给名为func的重载函数。只查看不同版本func的形参表来选择传递compare的哪个实例是不可能的,两个不同的实例都可能满足该调用。显式模板形参需要指出应使用哪个compare实例以及调用哪个func函数


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P542 习题16.23  
  2.     cout << max(3.14,5) << endl;    //Error  
  3.     cout << max<double>(5.56,4) << endl;  //OK  


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //习题16.24/25  
  2. template <typename T,typename U>  
  3. int compare(const T &val1,const U &val2)  
  4. {  
  5.     if (val1 < val2)  
  6.         return -1;  
  7.     if (val2 < val1)  
  8.         return 1;  
  9.     return 0;  
  10. }  
  11.   
  12. int main()  
  13. {  
  14.     cout << compare(1,static_cast<int>(3.14)) << endl;  
  15.     cout << compare(3.14,static_cast<double>(3)) << endl;  
  16.   
  17.     cout << compare<int>(1,3.14) << endl;  
  18.     cout << compare<double>(1,3.14) << endl;  
  19.   
  20.     cout << compare<string,string>("Hello","word") << endl;  
  21.     cout << compare(string("hello"),string("Word")) << endl;  
  22. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //习题16.26  
  2. template <typename T1,typename T2,typename T3>  
  3. T1 sum(T2,T3);  
  4.   
  5.   
  6. int main()  
  7. {  
  8.     double dobj1,dobj2;  
  9.     float fobj1,fobj2;  
  10.     char cobj1,cobj2;  
  11.   
  12.     sum(dobj1,dobj2);   //Error  
  13.     sum<double,double,double>(fobj1,fobj2);   //OK  
  14.     sum<int,int>(cobj1,cobj2);    //OK  
  15.     sum<double,,double>(fobj1,fobj2); //Error  
  16. }  

模板与泛型编程

--实例化[]



二、函数模板的显式实参

在某些情况下,不可能推断模板实参的类型。当函数的返回类型必须与形参表中所用的所有类型都不同时,最常出现这一问题。在这种情况下,有必要覆盖模板实参推断机制,显式指定为模板形参所用的类型或值



1、指定显式模板实参

如果函数形参类型不统一,该如何指定sum的返回类型?

[cpp] view plaincopy
  1. template <class T, class U>   
  2. ??? sum(T, U);   

此时使用任一形参调用都一定会在某些时候失败:

[cpp] view plaincopy
  1. sum(3, 4L); // 第二个类型较大,则应使用 U sum(T, U)  
  2. sum(3L, 4); // 第一个类型较大,则应使用 T sum(T, U)  

可以强制sum的调用者将较小的类型强制转换为希望作为结果使用的类型来解决此问题:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int i;  
  2. short s;  
  3. sum(static_cast<int>(s), i);  

2、在返回类型中使用类型形参

指定返回类型的一种方式是引入第三个模板形参,它必须由调用者显式指定:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <class T1,class T2,class T3>  
  2. T1 sum(T2,T3);  

有一个问题:没有实参的类型可用于推断T1的类型,相反,调用者必须在每次调用sum为该类型显式提供实参

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int i;  
  2. long lng;  
  3. long val = sum<long>(i,lng);  

这一调用显式指定T1的类型,编译器从调用中传递的实参推断T2T3的类型。

【小结】

显式模板实参从左至右与对应模板形参相匹配,假如可以从函数形参推断,则只有结尾(最右边)形参的显式模板实参可以忽略。

如果这样编写sum函数:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <class T1,class T2,class T3>  
  2. T3 alternative_sum(T2,T1);  

则总是必须为所有三个形参指定实参:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //没法识别模板实参的初始化  
  2. long val = alternative_sum<long>(i,lng);    //Error  
  3. long val2 = alternative_sum<long,int,long>(i,lng);  //OK  

3、显式实参与函数模板指针

可以使用显式模板实参的另一个例子是第16.2.1节中有二义性程序,通过使用显式模板实参能够消除二义性:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. template <typename T>  
  2. int compare(const T &,const T &);  
  3.   
  4. void func(int (*)(const string &,const string &));  
  5. void func(int (*)(const int &,const int &));  
  6.   
  7. func(compare<string>);  

像前面一样,需要在调用中传递compare实例给名为func的重载函数。只查看不同版本func的形参表来选择传递compare的哪个实例是不可能的,两个不同的实例都可能满足该调用。显式模板形参需要指出应使用哪个compare实例以及调用哪个func函数


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P542 习题16.23  
  2.     cout << max(3.14,5) << endl;    //Error  
  3.     cout << max<double>(5.56,4) << endl;  //OK  


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //习题16.24/25  
  2. template <typename T,typename U>  
  3. int compare(const T &val1,const U &val2)  
  4. {  
  5.     if (val1 < val2)  
  6.         return -1;  
  7.     if (val2 < val1)  
  8.         return 1;  
  9.     return 0;  
  10. }  
  11.   
  12. int main()  
  13. {  
  14.     cout << compare(1,static_cast<int>(3.14)) << endl;  
  15.     cout << compare(3.14,static_cast<double>(3)) << endl;  
  16.   
  17.     cout << compare<int>(1,3.14) << endl;  
  18.     cout << compare<double>(1,3.14) << endl;  
  19.   
  20.     cout << compare<string,string>("Hello","word") << endl;  
  21.     cout << compare(string("hello"),string("Word")) << endl;  
  22. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //习题16.26  
  2. template <typename T1,typename T2,typename T3>  
  3. T1 sum(T2,T3);  
  4.   
  5.   
  6. int main()  
  7. {  
  8.     double dobj1,dobj2;  
  9.     float fobj1,fobj2;  
  10.     char cobj1,cobj2;  
  11.   
  12.     sum(dobj1,dobj2);   //Error  
  13.     sum<double,double,double>(fobj1,fobj2);   //OK  
  14.     sum<int,int>(cobj1,cobj2);    //OK  
  15.     sum<double,,double>(fobj1,fobj2); //Error  
  16. }  


0 0
原创粉丝点击