C++-函数模板特化如何避免重复定义

2023-11-13

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

另一篇相关博文:https://blog.csdn.net/shixin_0125/article/details/78778234

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

Lee Kyung Jun


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

template <typename T>
int compare(T t1, T t2)
{
   return t1==t2 ? 0 : t1 > t2 ? 1 : -1;
}

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

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

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

// specialization for strings
template<>
int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)
{
    return _tcscmp(s1, s2);
}

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

int compare(LPCTSTR s1, LPCTSTR s2)
{
    return _tcscmp(s1, s2);
}

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

template<>
inline int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)
{
    return _tcscmp(s1, s2);
}

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

// in .h header file
template<>
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 特化。还有一个可选的方法是将所有东西放在一个文件中,并用预处理符号控制实例化:

#ifdef MYLIB_IMPLEMENT_FUNCS
template<>
int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)
{
    return _tcscmp(s1, s2);
}
#endif

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

template<>
static int compare<LPCTSTR>(LPCTSTR s1, LPCTSTR s2)
{
    return _tcscmp(s1, s2);
}

  这样链接器也不会出错,因为静态函数不向外界输出其函数,并且它让你将所有东西都保持在一个头文件中,不用引入预处理符号。但它缺乏效率,因为每个模块都有一个函数拷贝。如果函数小到没什么——那为何不用内联呢?
  所以简言之:将特化做成 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.部分特化的定义与通用模板的定义完全无

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++-函数模板特化如何避免重复定义 的相关文章

  • C++操作SQLite数据库

    准备工作 在使用C 操作SQLite之前 需要获得sqlite3 h sqlite3 lib sqlite3 dll 大家可以在 这里 下载 并将这3个文件导入VC 工程中 其中sqlite3 dll文件放到Debug文件夹里 SQLite
  • Qt5学习之路(vs2012下创建一个QT应用程序)2013-10-14

    刚开始学习QT在网上找的资料基本都是使用QT Create进行开发的 VS下开发的学习资料感觉很少很难找的到 视频教程也基本没看到过貌似 因为我们研发中心是使用MFC进行开发开发工具是VS2010 使用QT开发的话基本我们不会再使用QT C
  • JNA模拟复杂的C类型——Java映射char*、int*、float*、double*

    文章目录 引言 Java Native Type Conversions Java和C基本类型指针对应关系 Pointer的具体用法 引言 最近项目在用Java调用C写的一些三方库 没办法直接调 用Java封装一下C的接口 这就少不了要用到
  • C/C++中浮点数格式学习——以IEEE75432位单精度为例

    这是浮点数的通常表示形式 在IEEE754中 单精度浮点数有如下形式 32位单精度 单精度二进制小数 使用32个比特存储 1 8 23位长 S Exp Fraction 31 30至23偏正值 实际的指数大小 127 22至0位编号 从右边
  • C/C++ 引用作为函数的返回值

    语法 类型 函数名 形参列表 函数体 特别注意 1 引用作为函数的返回值时 必须在定义函数时在函数名前将 2 用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本 代码来源 RUNOOB include
  • 如何学好C语言的数据结构与算法?

    C语言的数据结构与算法 难就难在链表 学会了链表 可能后面就一点都不难了 书籍推荐 数据结构与算法分析 C语言描述版 要深入学习的话可以选择这本书 因为针对链表的讲解是比较详细的 所以可以很快理解链表 跟着书上一点点实现基本操作 增删改查
  • BP学习算法-构建三层神经网络

    引 人工神经网络 Artificial Neural Networks 简写为ANNs 也简称为神经网络 NNs 或称作连接模型 Connection Model 是一种模仿动物神经网络行为特征 进行分布式并行信息处理的算法数学模型 这种网
  • C++中的RTTI

    文章目录 dynamic cast运算符 指针类型的dynamic cast 引用类型的dynamic cast typeid运算符 使用RTTI type info类 参考资料 RTTI Runtime Type Information
  • LeetCode题目笔记——17.19消失的两个数字

    文章目录 题目描述 题目难度 困难 方法一 暴力 代码 代码优化 方法二 数学方法 代码 总结 题目描述 题目直达 题目难度 困难 方法一 暴力 虽然题目说你能在 O N 时间内只用 O 1 的空间找到它们吗 但是也没有限制我们不能用暴力
  • R----dplyr包介绍学习

    dplyr包 plyr包的替代者 专门面对数据框 将ddplyr转变为更易用的接口 gt 来自dplyr包的管道函数 其作用是将前一步的结果直接传参给下一步的函数 从而省略了中间的赋值步骤 可以大量减少内存中的对象 节省内存 可惜的是应用范
  • Trace Function Enter, Exit and Leave

    http developer nokia com community wiki Trace Function Enter Exit and Leave
  • 模板的完全特例化和部分特例化

    介绍 完全特例化就是类型完全明确的版本 而部分特例化指的是 只知道是几个参数的函数而不知道参数的类型 或者是只知道是引用或者是指针类型 而不知道具体是char 还是 int 模板特例化实例1 template
  • C++学习笔记12:输入输出流实例整理(文本文件读写,二进制文件读写,一组数据的文件读写,随机访问文件实例

    这也太难记了555老阔疼 文件读写示例 include
  • visual studio 一直显示正在准备解决方案

    首先重启电脑 无法解决的情况下执行以下步骤 Kill Visual Studio Open Visual Studio without loading a solution Disable AnkhSvn as Source Control
  • 【数据结构/C++】树和二叉树_二叉链表

    include
  • 【C++】运算符重载

    加号运算符重载 include
  • C/C++编程中的算法实现技巧与案例分析

    C C 编程语言因其高效 灵活和底层的特性 被广大开发者用于实现各种复杂算法 本文将通过10个具体的算法案例 详细探讨C C 在算法实现中的技巧和应用 一 冒泡排序 Bubble Sort 冒泡排序 Bubble Sort 是一种简单的排序
  • C 语言运算符详解

    C 语言中的运算符 运算符用于对变量和值进行操作 在下面的示例中 我们使用 运算符将两个值相加 int myNum 100 50 虽然 运算符通常用于将两个值相加 就像上面的示例一样 它还可以用于将变量和值相加 或者将变量和另一个变量相加
  • C++ 中 const 和 constexpr 关键字解析:常量、函数和指针

    很多 C 的初学者看到 const 这个关键字的第一反应都是一头雾水 主要是因为 const 可 以出现在很多的位置 以及后面加入的 constexpr 更是常常感到困惑 今天就为大家一一解释出现它们的含义和以及作用 const 关键字 c
  • C中的内存使用问题

    请帮忙 操作系统 Linux 其中 sleep 1000 中 此时 top 显示Linux任务 给我写了7 7 MEM使用 valgrind 未发现内存泄漏 我明白 写得正确 所有 malloc 结果都是 NULL 但是为什么这次 睡眠 我

随机推荐

  • mysql知识系列:数据库名称带减号- 创建修改删除时

    说明 create database a b 会提示报错 ERROR 1064 42000 You have an error in your SQL syntax check the manual that corresponds to
  • 使用navicat for mysql连接远程mysql

    我是使用navicat的windows端 连接centos下mysql服务器 其实配过远程连接grant all privileges on to root identified by password 并在服务器控制台打开3306端口就可
  • 服务器操作系统使用相关要求,服务器操作系统的安全要求

    服务器操作系统的安全要求 内容精选 换一换 本文介绍创建裸金属服务器的几种方式 按照向导指引创建裸金属服务器是常见的方式 您可以灵活选择配置项 确保满足业务的需求 详细操作请参见创建裸金属服务器 如果您想快速获取一台裸金属服务器 可以创建快
  • 基于vue的swiper动画轮播图

    以前做轮播都是参照 https www swiper com cn 去做 使用最多的是https github com surmon china vue awesome swiper 这个插件但是 因为介绍不够详细经常性遇到各种问题 直到几
  • [转] PyTorch 0.4新版本 升级指南 no_grad

    转自PyTorch 0 4新版本 升级指南 博主为ShellCollector PyTorch 0 4新版本 升级指南 PyTorch 终于从0 3 1升级到0 4 0了 首先引入眼帘的 是PyTorch官方对自己的描述的巨大变化 PyTo
  • K8s如何在不重新打版本号的情况 更新镜像

    1 重新部署的时候 打上版本号 是不会默认将镜像更新到最新 如果不打版本号 v3 0 11改为latest make docekr push会将镜像更新到最新 2 解决方法 手动将镜像拉到最新 1 用SecureCRT登录上k8s 找到部署
  • YouTube 的视频推荐算法

    转载 https www zhihu com question 20829671 answer 205421638 第一阶段 基于User Video图游历算法 2008年 1 在这个阶段 YouTube认为应该给用户推荐曾经观看过视频的同
  • C++ replace用法

    replace算法 replace函数包含于头文件 include中 泛型算法replace把队列中与给定值相等的所有值替换为另一个值 整个队列都被扫描 即此算法的各个版本都在 线性时间内执行 其复杂度为O n 即replace的执行要遍历
  • C#增删查改

    C 代码都是做后台数据处理的 它将浏览器与数据库互通形成一个动态数据的平台 而C 对于数据处理方式最多的就是查询 新增 修改 删除 以这四个方面为主做数据的处理 根据实际不同的使用 对这四个方法的使用难度也不一样 一 查询 查询方法使用是最
  • 50 亿观众的 “云上奥运”,顶级媒体背后的数智化力量

    东京 2020 奥运会即将闭幕 本届奥运会由于疫情限制 东京地区赛事以无观众的空场形式举行 在无法亲临现场的情况下 全球观众首次以 云上 方式观看奥运 云上奥运 该如何保证赛事的生动性和现场感 缩短观众与赛场之间的距离 随时随地捕捉精彩赛事
  • pydantic学习与使用-8.required-fields必填字段省略号( ...)

    前言 必填字段可以仅用注释来声明 也可以使用省略号 作为值 必填字段 必填字段 可以仅用注释来声明 以下name和age2个字段是必填字段 from pydantic import BaseModel class User BaseMode
  • 社区团购的运营模式是什么?

    社区团购是一种近年来兴起的新型电商模式 它通过社区的力量 以线上线下联动的方式将消费者聚集起来 以优惠的价格和更好的商品为社区居民提供服务 这种模式能够更好地满足社区居民的需求 并且可以有效地提高社区居民的生活质量 运营模式主要分为以下几个
  • 不懂23种设计模式?别灰心,这份核心笔记来帮你,你想知道的都在这里!

    设计模式是软件工程中各种常见问题的经典解决方案 设计模式不只是代码 而是组织代码的方式 假设一行行的代码是砖 设计模式就是蓝图 什么是设计模式 设计模式是解决问题的一种思想 和语言无关 在面向对象软件设计的工程中 针对特定的问题简洁优雅的一
  • Java实现五子棋小游戏(附思路讲解,全部代码,游戏截图)

    本文章是如何实现一个单机版双人五子棋小游戏 通过Swing技术进行可视操作 个人简介 个人主页 码云不秃头 本人是一名大三学生 马上就要变成考研狗啦 通过一学期对Java学习 经过老师的教学 实现单机版的双人五子棋小游戏 大家互相学习 也同
  • 一个web app有多主题,多环境

    在一个web app应用中 需要有多个运行环境 并且每个运行环境主题也是不一样 本项目解决方案 import http es6 ruanyifeng com docs module import ES6 import 可以动态加载 可以利用
  • Extjs入门

    1 什么是Extjs Ext JS 是一个强大的JavaScript类库 提供了丰富且美观的UI组件 和easyUI类似 但更强大 因而使用了Ext JS 您需要写的代码基本上是JavaScript 不需要写HTML 它主要用于创建前端用户
  • LLM推理部署(一):LLM七种推理服务框架总结

    自从ChatGPT发布以来 国内外的开源大模型如雨后春笋般成长 但是对于很多企业和个人从头训练预训练模型不太现实 即使微调开源大模型也捉襟见肘 那么直接部署这些开源大模型服务于企业业务将会有很大的前景 本文将介绍七中主流的LLM推理和服务开
  • 数字电路和模拟电路-10时序逻辑电路的分析和设计

    前言 学习同步时序逻辑电路的分析 设计 一 同步时序逻辑电路的分析 1 时序逻辑电路的分析步骤 步骤一 逻辑图 同步or异步 计数器or状态机 一条总线同步 多条总线是异步 计数器无输入 状态机有输入 状态机还分摩尔型和米里型 步骤二 驱动
  • zotero配置

    1 下载安装 2 配置坚果云同步 编辑 首选项 同步 输入zotero账户密码进行数据同步 文件同步选择坚果云同步 3 配置茉莉花插件 安装pdftk
  • C++-函数模板特化如何避免重复定义

    本文转自 https www cnblogs com dracohan p 3401660 html 转来收藏以便查阅 感谢原作者 另一篇相关博文 https blog csdn net shixin 0125 article detail