模板函数与特化函数

本文转自:https://www.cnblogs.com/dracohan/p/3401660.html  转来收藏以便查阅,感谢原作者

今天在写代码时,遇到了模板和特化,在网上找了资料后问题呗一一解决,转载此文用于以后查阅,感谢原创者。其中增加了我自己的总结:

特化函数与模板函数的区别:

(1)、模板函数的T参数只能传入类类型的参数;特化函数的参数只能传入对应的参数类型,基本类型或类类型。

(2)模板函数在link时检查类型,编译时只是当成了注释;特化函数及参数在编译时检查是否匹配或有错。

(3)、特化函数与模板函数重名时,特化函数需要加特殊处理。一种方式是加inline修饰,不在编译时生成对应的符号表。

 

我正在用一个基于模板的库源代码,该库包含一些针对特定类型的模板函数特化。类模板,函数模板和模板函数特化都在头文件中。我在我的.cpp文件中 #include 头文件并编译链接工程。但是为了在整个工程中使用该库,我将头文件包含在 stdafx.h 中,结果出现特化模板函数的符号多重定义错误。我要如何组织头文件才能避免多重符号定义错误?我用 /FORCE:MULTIPLE,但我想用一个更好的解决方法。

Lee Kyung Jun


 实际上,确实用更好的解决方法。稍后我会解释,但首先让我重温一下模板函数特化是如何工作的。假设你有一个比较两个基于 operator> 和 operator== 对象的模板函数:

 
  1. template <typename T>

  2. int compare(T t1, T t2)

  3. {

  4. return t1==t2 ? 0 : t1 > t2 ? 1 : -1;

  5. }

  该模板根据地一个参数是否等于、大于、或小于第二个参数而分别返回零或+/-1。它是典型的用于集合排序时的排序函数。它假设类型 T 具备 operator== 和 operator> 操作,并支持 int,float,double 或 DWORD 类型。但它不能应用于比较自负串(char* 指针),因为这个函数比较的是串指针,而不是字符串本身:

 
  1. LPCTSTR s1,s2;

  2. ...

  3. int cmp = compare(s1,s2); // s1<s2? Oops!

为了能进行字符串比较,你需要一个使用 strcmp 或其 TCHAR 版本 _tcscmp 的模板特化:

 
  1. // specialization for strings

  2. template<>

  3. int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)

  4. {

  5. return _tcscmp(s1, s2);

  6. }

  没错,这样做完全正确,现在的问题是:将这个特化放在何处?显然是要放在模板的头文件中。但这样会导致符号多重定义的错误,就像 Lee 遇到的那样。原因很明显,模板特化是一个函数,而非模板。它与下面的写法是一样的:

 
  1. int compare(LPCTSTR s1, LPCTSTR s2)

  2. {

  3. return _tcscmp(s1, s2);

  4. }

  没有理由不在头文件中定义函数——但是一旦这样做了,那么你便无法在多个文件中 #include 该头文件。至少,肯定会有链接错误。怎么办呢?
  如果你掌握了模板函数特化即函数,而非模板的概念,你就会认识到有三个选项,完全与普通函数一样;特化为 inline,extern 或者 static。例如,像下面这样:

 
  1. template<>

  2. inline int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)

  3. {

  4. return _tcscmp(s1, s2);

  5. }

  对于大多数模板库而言,这是最容易和最常见的解决方案。因为编译器直接扩展内联函数,不产生外部符号,在多个模块中 #include 它们没有什么问题。链接器不会出错,因为不存在多重定义的符号。对于像 compare 这样的小函数来说,inline 怎么说都是你想要的(它更快)。
  但是,如果你的特化很长,或出于某种原因,你不想让它成为 inline,那要如何做呢?此时可以做成 extern。语法与常规函数一样:

 
  1. // in .h header file

  2. template<>

  3. extern int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2);

  当然,你得在某个地方实现 compare。部分细节如 Figure 7 所示。我在单独的模块 Templ.cpp 中实现了特化,它与主工程链接。Templ.h 被 #include 在 stdafx.h 中,而 stdafx.h 又被 #include 在 Templ.cpp 和主模块两个文件中——生成工程没有链接错误。去下载源代码自己尝试一下吧。
  如果你正在为其他开发人员写模板库,extern 方式会很不爽,因为你必须创建一个带目标模块的链接库(lib),它包含有特化。如果你已经有了一个这样的 .lib,也没什么;如果没有,你可能会想方设法避免引入这样的库。仅用头文件实现模板是更好的方法(麻烦少)。最容易的方式是用 inline,此外,你还能将你的特化放在单独的头文件中,使之与其声明分开并要其他开发人员只在一个模块中 #include 特化。还有一个可选的方法是将所有东西放在一个文件中,并用预处理符号控制实例化:

 
  1. #ifdef MYLIB_IMPLEMENT_FUNCS

  2. template<>

  3. int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)

  4. {

  5. return _tcscmp(s1, s2);

  6. }

  7. #endif

  使用该方法,所有模块都包含此头文件,但在包含它之前,只有一个 #define MYLIB_IMPLEMENT_FUNCS。这个方法不支持预编译头,因为编译器用 stdafx.h 中的任何 MYLIB_IMPLEMENT_FUNCS 值加载预编译版本。
  避免符号多重定义错误的最后同时也是用得最少的一个方法是将特化做成 static:

 
  1. template<>

  2. static int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)

  3. {

  4. return _tcscmp(s1, s2);

  5. }

  这样链接器也不会出错,因为静态函数不向外界输出其函数,并且它让你将所有东西都保持在一个头文件中,不用引入预处理符号。但它缺乏效率,因为每个模块都有一个函数拷贝。如果函数小到没什么——那为何不用内联呢?
  所以简言之:将特化做成 inline 或 extern。通常都是用 inline。两种方法都得编辑头文件。如果使用的是第三方的库没有头文件,那么你除了用链接选项 /FORCE:MULTIPLE 之外别无选择。在你等着生成你的工程时,你可以告诉编写库文件的那个家伙——为什么要将函数模板特化定义成 inline 或者 extern。就说是我说的。

------------

 

c++模板概念

typename名字能更清楚的表明后面的名字是类型名,但是关键字typename是最近加入到标准C++中

(16)编译器如何分析模板定义:(编译时刻分析模板定义(注:不是模板实例化))
    对于编译器来说,它并不总是能够区分出模板定义中的哪些表达式是类型.
    
    为了让编译器能够分析模板定义用户必须指示编译器哪些表达式是类型表达式,
    告诉编译器一个表达式是类型表达式的机制是在表达式前加上关键字typename.
    
    
    
   

(17)模板类型参数:
   
   由关键字class 或typename 后加一个标识符构成.在函数的模板参数表中.
   
   这两个关键字的意义相同.它们表示后面的参数名代表一个潜在的内置或用户定义的类型,模板参数名由程序员选择.
   
   模板类型参数被用作一个类型指示符可以出现在模板定义的余下部分.
   
   1.模板类型参数名可以被用来指定函数模板的返回位.(函数的返回类型)
   2.模板参数名在同一模板参数表中只能被使用一次,但是模板参数名可以在多个函数模板声明或定义之间被重复使用.
   3.模板参数在函数参数表中可以出现的次数没有限制
   4.一个模板的定义和多个声明所使用的模板参数名无需相同
   5.如果一个函数模板有一个以上的模板类型参数,则每个模板类型参数前面都必须有关键字class 或typename.
   6.多个函数实参可以参加同一个模板实参的推演过程。如果模板参数在函数参数表中出现多次,则每个推演出来的类型都必须与根据模板实参推演出来的第一个类型完全匹配。
     这些可能的类型转换的限制只适用于参加模板实参推演过程的函数实参,对于所有其他实参所有的类型转换都是允许的.
   7.
   
(18)模板非类型参数:
    由一个普通的参数声明构成,模板非类型参数表示该参数名代表了一个
    潜在的值,而该值代表了模板定义中的一个常量.
    
    模扳非类型参数被
    用作一个常量值可以出现在模板定义的余下部分它可以用在要求常量的地方或许是在
    数组声明中指定数组的大小或作为枚举常量的初始值.
    
(19)模板的定义:
    关键字template 总是放在模板的定义与声明的最前面关键字后面是用逗号分隔的模板
    参数表template parameter list 它用尖括号<> 一个小于号和一个大于号括起来.
    该列表是模板参数表不能为空,模板参数可以是一个模板类型参数template type
    parameter 它代表了一种类型,也可以是一个模板非类型参数template nontype parameter
    它代表了一个常量表达式.

    函数定义或声明跟在模板参数表
    
(20)模板实例化:
    类型和值的替换过程被称为模板实例化template instantiation.
    
    函数模板指定了怎样根据一组或更多实际类型或值构造出独立的函数.这个构造过程被
    称为模板实例化template instantiation    
    
    这个过程是隐式发生的,它可以被看作是函数模板调用或取函数模板的地址的副作用。

(21)模板参数表:
    用逗号分隔的模板参数表template parameter list 它用尖括号<> 一个小于号和一个大于号括起来.
    该列表是模板参数表不能为空,模板参数可以是一个模板类型参数template type
    parameter 它代表了一种类型,也可以是一个模板非类型参数template nontype parameter
    它代表了一个常量表达式.

(22)函数参数表:

(23)模板实参推演:(函数的返回值类型能推演否?)
    
    用函数实参的类型来决定模板实参的类型和值的过程被称为模板实参推演template argument deduction.    
   
    我们也可以不依赖模板实参推演过程而是显式地指定模板实参。
    
    在取函数模板实例的地址时必须能够通过上下文环境为一个模板实参决定一个惟一的类型或值,
    如果不能决定出这个惟一的类型或值就会产生编译时刻错误.

    当函数模板被调用时,对函数实参类型的检查决定了模板实参的类型和值.这个过程被
    称为模板实参推演template argument deduction
    
    ****在模板实参推演期间决定模板实参的类型时编译器不考虑函数模板实例的返回类型。
    
    要想成功地进行模板实参推演,函数实参的类型不一定要严格匹配相应函数参数的类型.
    下列三种类型转换是允许的:
    1.左值转换:
    
    2.限定转换:
    
    3.到一个基类该基类根据一个类模板实例化而来的转换让:
   

(24)显式地指定模板实参
    
   在某些情况下编译器不可能推演出模板实参的类.
    
   在这种情况下我们需要改变模板实参推演机制,并使用显式指定explicitly specify
   模板实参.模板实参被显式指定在逗号分隔的列表中用尖括号<> 一个小于号和一个
   大于号括起来紧跟在函数模板实例的名字后面.

   但是当模板实参被显式指定时就没有必要推演模板实参了.
   
   我们必须指出显式模板实参应该只被用在完全需要它们来解决二义性或在模板实参
   不能被推演出来的上下文中使用模板实例时首先让编译器来决定模板实参的类型和值是
   比较容易的其次如果我们通过修改程序中的声明来改变在函数模板实例调用中的函数实参的类型则编译器会自动用不同的模板实参实例化函数模板而无需我们做任何事情另

   一方面如果我们指定了显式模板参数则必须检查显式模板实参对于函数实参的新类型是
   否仍然合适所以建议在可能的时候省略显式模板实参
    
(25)模板中返回值的问题    

(26)显式模板实参 c++ primer 3e (重要)

(27)C++模板编译模式template compilation model

(28)函数的"template参数推导机制"推而导之的只是参数,无法推导函数的返回值类型。

(29)函数模板显式特化c++ primer 3e

     在模板显式特化定义explicit specialization definition 中先是关键字template 和一对
     尖括号(<> 一个小于号和一个大于号),然后是函数模板特化的定义,该定义指出了模板
     名,被用来特化模板的模板实参以及函数参数表和函数体.

     1.我们也可以声明一个函数模板的显式特化而不定义
     2.在声明或定义函数模板显式特化时我们不能省略显式特化声明中的关键字template 及其后的尖括号类似地函数参数表也不能从特化声明中省略掉.
     3.但是如果模板实参可以从函数参数中推演出来则模板实参的显式特化可以从显式特化声明中省略
     4.
     
(30)类模板显式特化 与 Tratis c++ primer 3e 

(31)为类模板实例的一个成员提供一个特化定义

    显式特化定义包括关键字template 后跟一对尖括号(<>一个小于号和一个大于号)以及后面的类成员的特化定义.
   

(31)特化整个类模板

    1.只有当通用的类模板被声明(不一定被定义)之后它的显式特化才可以被定义.
      即,在模板被特化之前编译器必须知道类模板的名字.
      
    2.如果整个类被特化了,那么标记特化定义的符号template<>只能被放在类模板的显式
      特化的定义之前,类模板特化的成员定义不能以符号template<>作为打头.
      
(32)类模板部分特化
    
    还是一个模板,只是部分模板参数通过具体的类型特化了.
     
    如果类模板有一个以上的模板参数,则有些人就可能希望为一个特定的模板实参或者一
   组模板实参特化类模吧,而不是为所有的模板参数特化该类模板.即,有人可能希望提供这样一个模吧,
    
    它仍然是一个通用的模吧,只不过某些模板参数已经被实际的类型或值取代.
    
    通过使用类模板部分特化partial specialization ,这是有可能实现的.相比"通用模板定义 
    针对一组特定的模板实参被实例化之后的类版本"而言,类模板的部分特化可能被用来定义
    一个更加适当更加高效的实现版本.
    
    1.但是类模板部分特化的名字后面总是跟着一个模板实参表.
    2.而部分特化的模板参数表只列出模板实参仍然未知的那些参数.
    3.部分特化的定义与通用模板的定义完全无

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/463558.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

这样调试内核启动流程

内核生命周期uboot 打印完 Starting kernel . . .&#xff0c;就完成了自己的使命&#xff0c;控制权便交给了 kernel 的第一条指令&#xff0c;也就是下面这个函数init/main.casmlinkage __visible void __init start_kernel(void){...rest_init();}start_kernel 相当于内核的…

ios 图片添加阴影

2019独角兽企业重金招聘Python工程师标准>>> UIimageView *imageView [[UIImageView alloc ] init]; imageView.layer.shadowColor [UIColor blackColor].CGColor; imageView.layer.shadowOffset CGSizeMake(3,2); imageView.layer.shadowOpacity 0.6; imageVie…

asp.net定时执行任务-解决应用池回收问题----转载

在复杂的业务应用程序中&#xff0c;有时候会要求一个或者多个任务在一定的时间或者一定的时间间隔内计划进行&#xff0c;比如定时备份或同步数据库&#xff0c;定时发送电子邮件&#xff0c;定期处理用户状态信息&#xff0c;支付系统中定期同步异常账单等等&#xff0c;我们…

bool与string互转

今天在工作中遇到了将string转换成bool类型数据&#xff0c;查阅了工具书解决了问题&#xff0c;现将注意要点总结如下&#xff1a; 增加头文件&#xff1a;#include <sstream> 代码如下&#xff1a; 在codeblocks软件上测试结果如下&#xff1a; 使用者需要根据自己的实…

Go语言之高级篇beego框架之参数配置与路由配置

一、参数配置 beego默认会解析当前应用下的conf/app.conf文件 1.1、beego的参数配置 appname WEB httpport 8080 runmode dev 几种开发模式 [dev] httpprot 8080 [prod] httpport 8081 [test] httpport 8082 //备注&#xff1a; beego.AppConfig.String( "dev::m…

C++ int转string以及源码

今天遇到一个int类型数据转换为string&#xff0c;查了资料在c11标准中增加了全局函数std::to_string来实现该功能&#xff1a; string to_string (int val); string to_string (long val); string to_string (long long val); string to_string (unsigned val); string t…

EUREKA原理总结

Eureka高可用架构 https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance 上图中主要的名称说明&#xff1a; Register&#xff1a;EurekaClient注册&#xff08;Http请求&#xff09;到EurekaServer&#xff0c;EurekaClient会发送自己元数据(ip,port,主页等)&#xff0…

linux下安装oracle 11g R2

Linux环境配置 [c-sharp]view plaincopy OS:Fedora 15 DB:Oracle 11gR2 将Oracle安装到home/oracle_11目录 配置过程&#xff1a;本文来自Oracle官方文档网上资料 Oracle官方文档&#xff1a;http://www.oracle.com/pls/db112/homepage 1. 以root用户登录到Linux 2. 检查机器…

通俗理解数字签名,ssl数字证书和https

前言 最近在开发关于PDF合同文档电子签章的功能&#xff0c;大概意思就是在一份PDF合同上签名&#xff0c;盖章&#xff0c;使其具有法律效应。签章有法律效应必须满足两个条件&#xff1a; 能够证明签名&#xff0c;盖章者是谁&#xff0c;无法抵赖PDF合同在签章后不能被更改在…

linux 性能分析工具——perf

最近需要对linux下的开发的数据库应用程序进行性能调试&#xff0c;找到了该篇文章&#xff0c;保存下来为了以后便于查找&#xff0c;这篇是转载的perf文章&#xff0c;后续还有vtune相关的文章。 转载&#xff1a;https://blog.csdn.net/u014608280/article/details/8026571…

红外遥控

红外遥控简介红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。由于红外线遥控不具有像无线电遥控那样穿过障碍物去控制被控对象的能力&#xff0c;所…

使用jQuery Mobile移动开发框架将博客网站快速转化为Mobile网站

日期&#xff1a;2012-7-12 来源&#xff1a;GBin1.com 在线演示 jQuery Mobile是一个非常不错的移动端网站应用的解决方案&#xff0c;很多网站都使用jQuery Mobile来生成Mobile手机端的移动网站应用&#xff0c;在过去的GBin1博客文章中&#xff0c;我们曾经使用jQuery Mob…

NVLink技术及影响解析

1繁华的背面 最新GPU架构Pascal&#xff0c;能自己开上舞台的Audi A7&#xff0c;超过700名与会专家学者及技术人员&#xff0c;近百场学术会议和科研分享&#xff0c;连续两晚的happy hour、酒会和GTC Party……在一片喧嚣和欢乐当中&#xff0c;2014年度的GTC大会降下了帷幕。…

2022年结束了

在去年这个时候&#xff0c;我刚好也写了一篇这样的文章。再往前是2020年的总结年终了&#xff0c;肿一下斗转星移&#xff0c;我的这个公众号也陪伴着我经过了4年的时间&#xff0c;明年后&#xff0c;我也正式进入35岁程序员的行列&#xff0c;随时会受到命运对我的锤炼。庆幸…

STL容器之deque

双端队列&#xff1b;序列式容器(deque/vector)&#xff1b;底层分段连续 支持从双端进行插入和删除&#xff1b; 综合了vector和list的优点&#xff1b; 插入、删除、查找的平均时间复杂度都是O(1) 部分deque的操作如下&#xff1a; #include <stdlib.h> #include …

varnish-cache使用

Varnish Cache是一个web加速软件&#xff0c;用作web服务加速的反向代理&#xff0c;与Squid不同的是它建立在较新的系统内核调用上&#xff0c;并且主要是使用内存作为缓存&#xff0c;它现有的使用者有facebook等&#xff0c;据使用者反馈&#xff0c;其与Squid相比&#xff…

2020年文章汇总

据悉&#xff0c;深圳某工程师沦为C语言笔试枪手修改cmdline 把内存改成512MB上拉电阻的作用剖析C语言是如何画出这样的三角形的c语言画谢宾斯基三角形Linux字符设备驱动实例哦&#xff0c;这是桶排序回答一个微信好友的创业问题Linux-C编程 / 多线程 / 如何终止某个线程&#…

2018年文章汇总

Android ANR 实例分析Linux kernel计算某段代码运行时间Linux Kernel 发展和内核特点C/C函数指针与指针函数(二)老王带你理解算法复杂度O(1),O(N),O(N^2)Android NDK Tombstone/Crash 分析堆和栈的区别&#xff08;转过无数次的文章&#xff09;C语言scanf-周末杂想C语言-scanf…

Angular CLI的简单使用(1)

参考地址: https://v2.angular.cn/docs/ts/latest/cli-quickstart.html Angular CLI是一个命令行界面工具&#xff0c;它可以创建项目、添加文件以及执行一大堆开发任务&#xff0c;比如测试、打包和发布。 1. 请先在终端/控制台窗口中运行命令 node -v 和 npm -v, 如下图,没有…

在没有数据集的情况下使用数据表

使用数据适配器填充数据表 View Code using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.Data.SqlClient;namespace PopDataTable {class Program{static void Main(string[] args){string connString…