模板与泛型编程

--模板特化

引言:

我们并不总是能够写出对全部可能被实例化的类型都最合适的模板。某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一些情况下,能够利用关于类型的一些特殊知识,编写比从模板实例化来的函数更有效率的函数。

compare函数和 Queue类都是这一问题的好样例:与C风格字符串一起使用进,它们都不能正确工作。

compare函数模板:

  1. template <typename Type>
  2. int compare(const Type &v1,const Type &v2)
  3. {
  4. if (v1 < v2)
  5. return -1;
  6. if (v2 < v1)
  7. return 1;
  8. return 0;
  9. }

假设用两个constchar*实參调用这个模板定义,函数将比較指针值。它将告诉我们这两个指针在内存中的相对位置,但没有说明与指针所指数组的内容有关的不论什么事情

为了能够将compare函数用于字符串,必须提供一个知道如何比較C风格字符串的特殊定义。这些版本号是特化的,这一事实对模板的用户透明。对用户而言,调用特化函数或使用特化类,与使用从通用模板实例化的版本号无法差别。

一、函数模板的特化

模板特化:该定义中一个或多个模板实參的实际类型或实际值是指定的。特化形式例如以下:

1)keywordtemplate后面接一对空的尖括号(<>);

2)再接模板名和一对尖括号,尖括号里指定这个特化定义的模板形參;

3)函数形參表;

4)函数体。

当模板形參类型绑定到const char* 时,compare函数的特化:

  1. template<>
  2. int compare<const char *>(const char *const &v1,
  3. const char *const &v2)
  4. {
  5. return strcmp(v1,v2);
  6. }

特化的声明必须与相应的模板相匹配。当调用compare函数的时候,传给它两个字符指针,编译器将调用特化版本号。编译器将为随意其它实參类型(包括普通char*)调用泛型版本号:

  1. const char *p1 = "Hello",*p2 = "world";
  2. int i1,i2;
  3. cout << compare(p1,p2) << endl;
  4. cout << compare(i1,i2) << endl;

1、声明模板特化

与随意函数一样,函数模板特化能够声明而无须定义

  1. template<>
  2. int compare<const char *>(const char *const &v1,
  3. const char *const &v2);

模板特化必须总是包括空模板形參说明符,即template<>,并且,还必须包括函数形參表。假设能够从函数形參表判断模板实參,则不必显式指定模板实參:

  1. int compare<const char *>(const char *const &v1,
  2. const char *const &v2); //Error
  3.  
  4. template<>
  5. int compare<const char *>; //OK
  6.  
  7. template<>
  8. int compare(const char *const &v1,
  9. const char *const &v2); //OK

2、函数重载与模板特化

在特化中省略空的模板形參表template<>会有令人吃惊的结果。假设缺少该特化语法,则结果是声明该函数的重载非模板版本号:

  1. template<>
  2. int compare(const char *const &v1,
  3. const char *const &v2);
  4.  
  5. int compare(const char *const &v1,
  6. const char *const &v2); //OK:可是是非模板的重载版本号!

当定义非模板函数的时候,对实參应用常规转换;当特化模板的时候,对实參类型不应用转换。在模板特化版本号的调用中,实參类型必须与特化版本号函数的形參类型全然匹配,假设不全然匹配,编译器将为实參从模板定义实例化一个实例。

3、不是总能检測到反复定义

假设程序由多个文件构成,模板特化的声明必须在使用该特化的每一个文件里出现。不能在一些文件里从泛化模板定义实例化一个函数模板而在其它文件里为同一个模板实參集合特化该函数版本号

【最佳实践】

与其它函数声明一样,应在一个头文件里包括模板特化的声明,然后使用该特化的每一个源文件包括该文件!

4、普通作用域规则用于特化

在能够声明或定义特化之前,它所特化的模板的声明必须在作用域中。类似的,在调用模板的这个版本号之前,特化的声明必须在作用域中:

  1. template <typename Type>
  2. int compare(const Type &v1,const Type &v2)
  3. {
  4. cout << "generic" << endl;
  5. if (v1 < v2)
  6. return -1;
  7. if (v2 < v1)
  8. return 1;
  9. return 0;
  10. }
  11.  
  12. int main()
  13. {
  14. int i = compare("Hello","World"); //print “generic”
  15. }
  16.  
  17. template<>
  18. int compare<const char *>(const char *const &v1,
  19. const char *const &v2)
  20. {
  21. cout << "hah" << endl;
  22. return strcmp(v1,v2);
  23. }

这个程序有错误,由于在声明特化之前,进行了能够与特化相匹配的一个调用。当编译器看到一个函数调用时,它必须知道这个版本号须要特化,否则,编译器将可能从模板定义实例化该函数。

对具有同一模板实參集的同一模板,程序不能既有显式特化又有实例化。

特化出如今对该模板实例的调用之后是错误的

  1. //P567 习题16.52/53/54
  2. template <typename ValType>
  3. size_t count(const vector<ValType> &vec,const ValType &val)
  4. {
  5. size_t appers = 0;
  6. for (typename vector<ValType>::const_iterator
  7. iter = vec.begin();
  8. iter != vec.end(); ++iter)
  9. {
  10. if (*iter == val)
  11. {
  12. ++ appers;
  13. }
  14. }
  15.  
  16. return appers;
  17. }
  18.  
  19. template <>
  20. size_t count(const vector<string> &vec,const string &val);
  21.  
  22. int main()
  23. {
  24. ifstream inFile("input");
  25. vector<int> ivec;
  26. int val;
  27. while (inFile >> val)
  28. {
  29. ivec.push_back(val);
  30. }
  31.  
  32. while (cin >> val)
  33. {
  34. cout << count(ivec,val) << endl;
  35. }
  36. }
  37.  
  38. template <>
  39. size_t count(const vector<string> &vec,const string &val)
  40. {
  41. size_t appers = 0;
  42. for (typename vector<string>::const_iterator
  43. iter = vec.begin();
  44. iter != vec.end(); ++iter)
  45. {
  46. if (*iter == val)
  47. {
  48. ++ appers;
  49. }
  50. }
  51.  
  52. return appers;
  53. }

二、类模板的特化

当用于C风格字符串时,Queue类具有与compare函数类似的问题。在这样的情况下,问题出在push函数中,该函数复制给定值以创建Queue中的新元素。默认情况下,复制C风格字符串仅仅会复制指针,不会复制字符。这样的情况下复制指针将出现共享指针在其它环境中会出现的全部问题,最严重的是,假设指针指向动态内存,用户就有可能删除指针所指的数组

1、定义类特化

为C风格字符串的Queue提供正确行为的一种途径,是为constchar *定义整个类的特化版本号:

  1. template<> class Queue<const char *>
  2. {
  3. public:
  4. void push(const char *);
  5. void pop()
  6. {
  7. real_queue.pop();
  8. }
  9. bool empty() const
  10. {
  11. return real_queue.empty();
  12. }
  13. std::string front()
  14. {
  15. return real_queue.front();
  16. }
  17. const std::string front() const
  18. {
  19. return real_queue.front();
  20. }
  21.  
  22. private:
  23. Queue<std::string> real_queue;
  24. };

这个实现了Queue一个数据元素:string对象的Queue。各个成员将它们的工作委派给这个成员

Queue类的这个版本号未定义复制控制成员,它唯一的数据成员为类类型,该类类型在被复制、被赋值或被撤销时完毕正确的工作。能够使用合成的复制控制成员

这个Queue类实现了与Queue的模板版本号大部分同样但不全然同样的接口,差别在于front成员返回的是string而不是char*,这样做是为了避免必须管理字符数组——假设想要返回指针,就须要字符数组。

值得注意的是:特化能够定义与模板本身全然不同的成员。假设一个特化无法从模板定义某个成员,该特化类型的对象就不能使用该成员类模板成员的定义不会用于创建显式特化成员的定义。

【最佳实践】

类模板特化应该与它所特化的模板定义同样的接口,否则当用户试图使用未定义的成员时会感到奇怪。


2类特化定义

在类特化外部定义成员时,成员之前不能加template<>标记。

  1. void Queue<const char *>::push(const char *p)
  2. {
  3. return real_queue.push(p);
  4. }

尽管这个函数差点儿没有做什么工作,但它隐式复制了val指向的字符数组。复制是在对real_queue.push的调用中进行的,该调用从const char * 实參创建了一个新的string对象。const char  * 实參使用了以const char  * 为參数的string构造函数,string构造函数将val所指的数组中的字符拷贝到未命名的string对象,该对象将被存储在push到 real_queue的元素中。

C++ Primer 学习笔记_84_模板与泛型编程 --模板特化的更多相关文章

  1. C++ Primer 学习笔记_76_模板与泛型编程 --模板定义[续]

    模板与泛型编程 --模板定义[续] 四.模板类型形參 类型形參由keywordclass或 typename后接说明符构成.在模板形參表中,这两个keyword具有同样的含义,都指出后面所接的名字表示 ...

  2. C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]

    模板与泛型编程 --模板特化[续] 三.特化成员而不特化类 除了特化整个模板之外,还能够仅仅特化push和pop成员.我们将特化push成员以复制字符数组,而且特化pop成员以释放该副本使用的内存: ...

  3. C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

    模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模 ...

  4. C++ Primer 学习笔记_76_模板和泛型编程 --模板定义[继续]

    模板和泛型编程 --模板定义[续] 四.模板类型形參 类型形參由keywordclass或 typename后接说明符构成.在模板形參表中,这两个keyword具有同样的含义,都指出后面所接的名字表示 ...

  5. C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型

    模板与泛型编程 --模板编译模型 引言: 当编译器看到模板定义的时候,它不马上产生代码.仅仅有在用到模板时,假设调用了函数模板或定义了模板的对象的时候,编译器才产生特定类型的模板实例. 一般而言,当调 ...

  6. C++ Primer学习笔记(三) C++中函数是一种类型!!!

    C++中函数是一种类型!C++中函数是一种类型!C++中函数是一种类型! 函数名就是变量!函数名就是变量!函数名就是变量! (---20160618最新消息,函数名不是变量名...囧) (---201 ...

  7. C++ Primer学习笔记(二)

    题外话:一工作起来就没有大段的时间学习了,如何充分利用碎片时间是个好问题. 接  C++ Primer学习笔记(一)   27.与 vector 类型相比,数组的显著缺陷在于:数组的长度是固定的,无法 ...

  8. C++ Primer 学习笔记_77_模板与泛型编程 --实例化

    模板与泛型编程 --实例化 引言: 模板是一个蓝图,它本身不是类或函数.编译器使用模板产生指定的类或函数的特定版本号.产生模板的特定类型实例的过程称为实例化. 模板在使用时将进行实例化,类模板在引用实 ...

  9. 【c++ Prime 学习笔记】第16章 模板与泛型编程

    面向对象编程(OOP)和泛型编程(GP)都能处理在编写程序时类型未知的情况 OOP能处理运行时获取类型的情况 GP能处理编译期可获取类型的情况 标准库的容器.迭代器.算法都是泛型编程 编写泛型程序时独 ...

随机推荐

  1. MySQL 可以用localhost 连接,但不能用IP连接的问题,局域网192.168.*.* 无法连接mysql

    Mysql 默认是没有开启这个权限的(只允许使用 host:localhost,或者 host:127.0.0.1),如果想用 host:192.168.1.* ,来访问mysql ,需要手动开启这个 ...

  2. (转)JSON对象长度和遍历方法

    最近在修改一个HTML页面的JS的时候遍历JSON对象,却怎么也调试不通过.怪这个HTML网页不知道用了什么方法禁止了js错误提示,刚开始的时候不知道有这个问题,用chrome的开发人员工具都没发现错 ...

  3. Oracle除去换行符的方法

    Oracle除去换行符的方法   很多数据存进数据库后,可能需要将整条数据取出,并用特殊 符号分割,而且整条数据必须是处于一行,如此,如果数据出现 换行的情况,那么读取时就有问题.     这个时候就 ...

  4. iOS_SN_地图的使用(2)

    上一篇讲的是地图的基本使用,和注意事项,这一篇主要讲POI检索.百度地图SDK提供三种类型的POI检索:周边检索.区域检索和城市内检索.下面将以周边检索为例,向大家介绍如何使用检索服务. - (voi ...

  5. params参数的使用方法

    params 将方法中实际参数列表中跟可变参数数组类型一致的类型,都处理为数组中的的元素 static void Main(string[] arr) { // int[] numbers={2,3, ...

  6. static静态类与非静态类的区别

    static静态类与非静态类的区别 1.在非静态类中可以有实例成员也可以有静态成员 2.在调用的时候需要使用对像名.实例成员调用(先要实例化,如person ps=new person();  ps. ...

  7. php 中PHP_EOL使用

    一个小小的换行,其实在不同的平台有着不同的实现,为什么要这样,可以是世界是多样的.本来在unix世界换行就用/n来代替,但是windows为了体现他的不同,就用/r/n,更有意思的是在mac中用/r. ...

  8. Android性能优化学习

    工作以来,越来越觉得性能优化的重要性,从技术角度,它甚至成了决定一个app成败的最关键因素.因此,特地花时间去学习专研性能优化的方法. 学习性能优化最便捷的方式便是研读别人有关性能优化的博客,然而网上 ...

  9. set{变量 = value;}get{return 变量;}

    形如: public string _customValue        {            set { _customValue = value; }            get { re ...

  10. 响应式Asp.net MVC企业网站源码

    最近时间充裕,自己写了一个响应式MVC企业网站系统,用于回顾自己的MVC知识.网站源码后台和前台都采用响应式布局,可以适应不同的屏幕. 一.源码描述 响应式企业网站系统,前台和后台都采用了响应式布局, ...