我正在用一个基于模板的库源代码,该库包含一些针对特定类型的模板函数特化。类模板,函数模板和模板函数特化都在头文件中。我在我的.cpp文件中 #include 头文件并编译链接工程。但是为了在整个工程中使用该库,我将头文件包含在 stdafx.h 中,结果出现特化模板函数的符号多重定义错误。我要如何组织头文件才能避免多重符号定义错误?我用 /FORCE:MULTIPLE,但我想用一个更好的解决方法。
实际上,确实用更好的解决方法。稍后我会解释,但首先让我重温一下模板函数特化是如何工作的。假设你有一个比较两个基于 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.只有当通用的类模板被声明(不一定被定义)之后它的显式特化才可以被定义. |
C++-函数模板特化如何避免重复定义的更多相关文章
-
C++实验二——函数重载、函数模板、简单类的定义和实现
一.实验过程 函数重载编程练习 实验要求:编写重载函数add(),实现对int型,double型,complex型数据的加法.在main函数中定义不同类型的数据,调用测试. 代码实现: 先是简单的体验 ...
-
C++ 实验2:函数重载、函数模板、简单类的定义和实现
1.函数重载编程 编写重载函数add(),实现对int型,double型,Complex型数据的加法.在main()函数中定义不同类型数据,调用测试. #include <iostream> ...
-
C++ template —— 模板特化(五)
本篇讲解模板特化-------------------------------------------------------------------------------------------- ...
-
C++ Primer 学习笔记_84_模板与泛型编程 --模板特化
模板与泛型编程 --模板特化 引言: 我们并不总是能够写出对全部可能被实例化的类型都最合适的模板.某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一 ...
-
C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]
模板与泛型编程 --模板特化[续] 三.特化成员而不特化类 除了特化整个模板之外,还能够仅仅特化push和pop成员.我们将特化push成员以复制字符数组,而且特化pop成员以释放该副本使用的内存: ...
-
C++ 模板特化以及Typelist的相关理解
近日,在学习的过程中第一次接触到了Typelist的相关内容,比如Loki库有一本Modern C++ design的一本书,大概JD搜了一波没有译本,英文版600多R,瞬间从价值上看到了这本书的价值 ...
-
C++模板之隐式实例化、显示实例化、隐式调用、显示调用和模板特化详解
模板的实例化指函数模板(类模板)生成模板函数(模板类)的过程.对于函数模板而言,模板实例化之后,会生成一个真正的函数.而类模板经过实例化之后,只是完成了类的定义,模板类的成员函数需要到调用时才会被初始 ...
-
C++函数模板详解(一):概念和特性
函数模板是指这样的一类函数:可以用多种不同数据类型的参数进行调用,代表了一个函数家族.它的外表和普通的函数很相似,唯一的区别就是:函数中的有些元素是未确定的,这些元素将在使用的时候才被实例化.先来看一 ...
-
让gcc支持成员函数模板的trick
让gcc支持成员函数模板的trick 罗朝辉 (http://www.cnblogs.com/kesalin/) 本文遵循“署名-非商业用途-保持一致”创作公用协议 gcc 4.7.3 不支持成员 ...
随机推荐
-
(转载)SQL Reporting Services (Expression Examples)
https://msdn.microsoft.com/en-us/library/ms157328(v=SQL.100).aspx Expressions are used frequently in ...
-
Android深度探索(卷1)HAL与驱动开发
第一章 介绍Android驱动开发和移植技术 主要对android和linux做了总体的介绍,让我们有了个感性的认识. 一.Android的四层系统架构: a) Linux内核:Android是基于L ...
-
Codefoces 432 C. Prime Swaps
哥德巴赫猜想: 任一大于2的偶数,都可表示成两个素数之和. 任一大于5的整数都可写成三个质数之和. 贪心取尽可能大的素数..... C. Prime Swaps time limit per test ...
-
学习ExpressionTree(做装配脑袋出的练习题)
1 // 第一题:画出下列表达式的表达式树.一开始,您很可能不知道某些操作其实也是表达式(比如取数组的运算符a[2]),不过没有关系,后面的习题将帮你验证这一点. //-a ParameterExpr ...
-
Ubuntu14.04下搜狗输入法的安装及配置
在搜狗官网上下载相应的版本32/64 搜狗网址:http://pinyin.sogou.com/linux/?r=pinyin 在文件夹中找到下载的搜狗输入法文件(默认位置是Downloads),双击 ...
-
【微信小程序开发】秒懂,架构及框架
今天1024程序员节,写文章庆祝!!! 今天的文章是讲微信小程序开发的,按理解把架构与框架说说.有不对之处请大神指点…… 微信小程序与web应用很像,但是原理不同,微信小程序是运行在微信应用内的,不是 ...
-
006.Zabbix添加监控主机
一 配置步骤和流程 Zabbix完整的监控配置流程可以简单的描述为: Host groups(主机组)---->Hosts(主机)---->Applications(监控项组)----&g ...
-
第 4 章—— C# 语言特性(《精通 ASP.NET MVC 5》)
这里只提供各个特性的简单概括. C# 的完整指南可参阅<Introducing Visual C#>.深度了解 LINQ 可参考<Pro LINQ in C#> 4.1 准备示 ...
-
(2017)你最不建议使用的Python Web框架?
https://www.sohu.com/a/164042813_737973 挺有意思的 经过一周的Django学习,以及对比,最终选定了以Flask入手来学习Python web开发.
-
Plugins in Unity
[Plugins in Unity] In Unity, you normally use scripts to create functionality but you can also inclu ...