模板与泛型编程

--模板特化

引言:

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

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

compare函数模板:

template <typename Type>
int compare(const Type &v1,const Type &v2)
{
if (v1 < v2)
return -1;
if (v2 < v1)
return 1;
return 0;
}

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

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

一、函数模板的特化

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

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

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

3)函数形參表;

4)函数体。

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

template<>
int compare<const char *>(const char *const &v1,
const char *const &v2)
{
return strcmp(v1,v2);
}

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

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

1、声明模板特化

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

template<>
int compare<const char *>(const char *const &v1,
const char *const &v2);

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

int compare<const char *>(const char *const &v1,
const char *const &v2); //Error template<>
int compare<const char *>; //OK template<>
int compare(const char *const &v1,
const char *const &v2); //OK

2、函数重载与模板特化

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

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

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

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

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

【最佳实践】

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

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

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

template <typename Type>
int compare(const Type &v1,const Type &v2)
{
cout << "generic" << endl;
if (v1 < v2)
return -1;
if (v2 < v1)
return 1;
return 0;
} int main()
{
int i = compare("Hello","World"); //print “generic”
} template<>
int compare<const char *>(const char *const &v1,
const char *const &v2)
{
cout << "hah" << endl;
return strcmp(v1,v2);
}

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

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

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

//P567 习题16.52/53/54
template <typename ValType>
size_t count(const vector<ValType> &vec,const ValType &val)
{
size_t appers = 0;
for (typename vector<ValType>::const_iterator
iter = vec.begin();
iter != vec.end(); ++iter)
{
if (*iter == val)
{
++ appers;
}
} return appers;
} template <>
size_t count(const vector<string> &vec,const string &val); int main()
{
ifstream inFile("input");
vector<int> ivec;
int val;
while (inFile >> val)
{
ivec.push_back(val);
} while (cin >> val)
{
cout << count(ivec,val) << endl;
}
} template <>
size_t count(const vector<string> &vec,const string &val)
{
size_t appers = 0;
for (typename vector<string>::const_iterator
iter = vec.begin();
iter != vec.end(); ++iter)
{
if (*iter == val)
{
++ appers;
}
} return appers;
}

二、类模板的特化

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

1、定义类特化

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

template<> class Queue<const char *>
{
public:
void push(const char *);
void pop()
{
real_queue.pop();
}
bool empty() const
{
return real_queue.empty();
}
std::string front()
{
return real_queue.front();
}
const std::string front() const
{
return real_queue.front();
} private:
Queue<std::string> real_queue;
};

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

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

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

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

【最佳实践】

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


2类特化定义

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

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

尽管这个函数差点儿没有做什么工作,但它隐式复制了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. iOS集成微信支付各种坑收录

    统一下单的参数要拼接成XML格式,使用AFN请求时要对参数转义,直接传入字典给AFN无法识别(这个接口微信demo中并没有提供示例) AFHTTPRequestOperationManager *ma ...

  2. mybati之运行过程

    mybatis其实就只有两个配置文件(mybatis-config.xml与mapper.xml) mybatis-config.xml配置基本的数据,和数据源,全局参数 mapper.xml 多个s ...

  3. JAVA--好友界面面板

    package GongYou; //package windows.best_demo; import java.awt.*; import javax.swing.*; import java.u ...

  4. HTML5 Canvas图像放大、移动实例1

    1.前台代码 <canvas id="canvasOne" class="myCanvas" width="500" height=& ...

  5. (转)HiddenField控件的使用

    ASP.NET2.0 HiddenField控件(1)2007-05-12 23:18HiddenField控件顾名思义就是隐藏输入框的服务器控件,它能让你保存那些不需要显示在页面上的且对安全性要求不 ...

  6. Jenkins学习之——(2)插件的安装

    本章节将讲解如何安装jenkins的插件. 其实jenkins本身不具有任何集成的功能,而是依靠众多的插件实现功能.就像eclipse一样,期本身只是一个编辑器,而当你安装了其他的第三方插件后,就能实 ...

  7. GitHub 相关内容

    1. Git是分布式版本控制系统 集中式版本控制系统:版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中 ...

  8. [置顶] chinayaosir近10年来所阅读的世界著名IT书籍-图文并茂

    1.人生观(包括做人原则,心理学,投资,销售) 一个人从来到世上,很多东西都是空白, 阅读一些正能量的书籍,把里面的理论用于生活,不断的应用它, 这些观念就会如同软件一样,不断的升级你的大脑, 合理的 ...

  9. SQL GROUP BY 语句

    合计函数 (比如 SUM) 常常需要添加 GROUP BY 语句. GROUP BY 语句 GROUP BY 语句用于结合合计函数,根据一个或多个列对结果集进行分组. SQL GROUP BY 语法 ...

  10. mysql 5.6 参数详解

    系统变量提供的是各种与服务器配置和功能有关的信息.大部分的系统变量都可以在服务器启动时进行设置.在运行时,每一个系统变量都拥有一个全局值或会话值,或者同时拥有这两个值.许多系统变量都是动态的,也就是说 ...