C++-函数模板特化如何避免重复定义
原创本文转自: 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
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
为了执行字符串比较,您需要一个 strcmp 或其 TCHAR 版本 _tcscmp 模板专用化:
// specialization for strings
template<>
int compare(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 s1, LPCTSTR s2)
{
return _tcscmp(s1, s2);
}
对于大多数模板库,这是最简单和最常见的解决方案。由于编译器直接扩展了内联函数,因此不会生成外部符号 #include 他们没有问题。链接器不会失败,因为没有多个定义的符号。对于像这样的事情 compare 对于这么小的功能,inline 无论你说什么,你都想要什么(它更快)。
但是,如果你的专业化很长,或者出于某种原因,你不希望它成为 inline那么我们该怎么做呢?此时,它可以制成 extern。语法与常规函数相同:
// in .h header file
template<>
extern int compare(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 s1, LPCTSTR s2)
{
return _tcscmp(s1, s2);
}
#endif
使用此方法,所有模块都包含此头文件,但在包含它之前,只有一个 #define MYLIB_IMPLEMENT_FUNCS。此方法不支持预编译标头,因为编译器使用 stdafx.h 中的任何 MYLIB_IMPLEMENT_FUNCS 值加载预编译版本。
避免符号中多个定义错误的最后一个也是最不常用的方法是将它们专门化为 static:
template<>
static int compare(LPCTSTR s1, LPCTSTR s2)
{
return _tcscmp(s1, s2);
}
这样,链接器就不会出错,因为静态函数不会将其函数输出到外部世界,并且它允许您将所有内容保存在头文件中,而无需引入预处理符号。但它缺乏效率,因为每个模块都有一个函数副本。如果函数太小而不重要 - 为什么不内联?
简而言之:专注于 inline 或 extern。通常使用 inline。这两种方法都需要编辑头文件。如果您使用的是没有头文件的第三方库,则可以改用链接选项 /FORCE:MULTIPLE 别无选择。当你等待生成你的项目时,你可以告诉编写库文件的人为什么你需要将函数模板专用化 inline 或者 extern。就说是我吧。
typename名称可以更清楚地表明以下名称是类型名称,但是关键字typename最近添加到标准中C++中
(16)编译器如何分析模板定义:(编译时间分析模板定义(注意:不是模板实例化))
对于编译器,它并不总是区分模板定义中的哪些表达式属于类型.
为了使编译器分析模板定义,用户必须向编译器指示哪些表达式是类型表达式,
告诉编译器表达式是类型表达式的机制是在表达式之前添加关键字typename.
(17)模板类型参数:
由关键字class 或typename 通过在它后面添加标识符来组成.在函数的模板参数表中.
这两个关键字具有相同的含义.它们表示由以下参数名称表示的潜在内置或用户定义类型,模板参数名称由程序员选择.
模板类型参数用作类型指示符,可以显示在模板定义的其余部分.
1.模板类型参数名称可用于指定函数模板的返回位.(函数的返回类型)
2.模板参数名称在同一模板参数表中只能使用一次,但是,模板参数名称可以在多个函数模板声明或定义之间重用.
3.模板参数在函数参数表中出现的次数没有限制
4.模板的定义和用于多个声明的模板参数名称不需要相同
5.如果函数模板具有多个模板类型参数,然后,每个模板类型参数之前都必须有一个关键字class 或typename.
6.多个函数实参可以参加同一个模板实参的推演过程。如果模板参数在函数参数表中出现多次,则每个推演出来的类型都必须与根据模板参数推导出来的第一个类型完全匹配。
这些可能的类型转换的限制只适用于参加模板参数推导过程的函数实参,所有其他参数都允许所有类型转换.
(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.但是,如果可以从函数参数推断模板参数,则可以从显式专用化声明中省略模板参数的显式专用化
(30)类模板的显式专用化 与 Tratis c++ primer 3e
(31)为类模板实例的成员提供专用定义
明确的专业化定义包括关键字template 后跟一对尖括号(<>小于号和大于号)以及后续类成员的专门定义.
(31)专用化整个类模板
1.仅当声明通用类模板时(不一定定义)只有这样,才能定义其明确的专业化.
即,编译器必须知道类模板的名称,然后才能对模板进行专用化.
2.如果整个班级都是专门的,因此,标记由专业化定义的符号template<>只能显式放置在类模板中
在专业化定义之前,类模板专用化的成员定义不能由符号表示template<>作为打头.
(32)类模板部分专用化
仍然是一个模板,只有一些模板参数通过特定类型进行了专用化.
如果类模板具有多个模板参数,有些人可能希望提供特定的模板参数或
组模板参数专用化类模板栏,而不是为所有模板参数专门使用这种类型的模板.即,有人可能想提供这样的模型,对吧,
它仍然是一个通用模块,对吧,但是,某些模板参数已替换为实际类型或值.
通过使用类模板进行部分专用化partial specialization ,这是可能的.相比"常规模板定义
实例化一组特定模板参数后的类版本"而言,类模板的部分专用化可用于定义
更合适、更高效的实现版本.
1.但是类模板的部分专用化的名称总是跟着模板参数表.
2.部分专用模板参数表仅列出模板参数仍然未知的参数.
3.部分专业化的定义与通用模板的定义完全不同
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除
itfan123


