为什么在涉及多个定义时,非模板函数的处理方式与模板不同?
这里涉及到历史和兼容性问题。一些需求来自 C,这就是它的工作方式。还有一些与模板是什么有关的原因,它们是代码生成器;需要时,编译器需要生成代码,因此它需要在生成代码时查看代码。这会产生连锁反应,即会有多个定义,因此需要规则来解决这些问题。
简单地说;模板的行为(w.r.t. 链接)就好像它们在程序中有一个定义一样,因此它们在编译和链接期间的行为与非模板(未使用 inline 声明的)不同 - 特别是 w.r.t.职能。如果将非模板声明为 inline,则会看到类似的行为。
这里的标准参考包括;
一些背景知识,这里的大部分问题都与linkage有关,linkage是什么? §3.5/2 [basic.link]
当一个名称可能表示与另一个范围内的声明引入的名称相同的对象、引用、函数、类型、模板、命名空间或值时,就说它具有 链接:
- 当名称具有外部链接时,它所表示的实体可以由其他翻译单元的范围或同一翻译单元的其他范围的名称引用。
- 当名称具有内部链接时,它所表示的实体可以由同一翻译单元中其他范围的名称引用。
- 当一个名称没有链接时,它所表示的实体不能被其他范围的名称引用。
关于函数和变量的一些一般规则,适用于整个程序和每个翻译单元。
§3.2/1 [basic.def.odr]
任何翻译单元不得包含多个定义
变量、函数、类类型、枚举类型或模板。
和
§3.2/4 [basic.def.odr]
每个程序都应包含该程序中 odr 使用的每个非内联函数或变量的一个定义...
§3.2/6 [basic.def.odr]
类类型(子句 [class])、枚举类型([dcl.enum])、带有外部链接的内联函数([dcl.fct.spec])、类模板(子句)可以有多个定义[temp])、非静态函数模板 ([temp.fct])、类模板的静态数据成员 ([temp.static])、类模板的成员函数 ([temp.mem.func]),或如果每个定义出现在不同的翻译单元中,并且定义满足以下要求,则在程序中未指定某些模板参数([temp.spec],[temp.class.spec])的模板专业化... .
如果D 是一个模板并且在多个翻译单元中定义,那么前面的要求既适用于模板定义中使用的模板封闭范围的名称([temp.nondep]),也适用于实例化点的依赖名称([temp.dep])。如果D 的定义满足所有这些要求,那么行为就像有一个D 定义一样。如果D 的定义不满足这些要求,则行为未定义。
对上面列表的一些非正式观察,包括类、模板等。这些是通常在头文件中找到的典型元素(当然不限于或仅限于头文件)。他们被赋予了这些特殊规则,以使一切按预期工作。
类成员函数呢? §9.3 [class.mfct]
1/ 成员函数可以在其类定义中定义([dcl.fct.def]),在这种情况下,它是一个内联成员函数([dcl.fct.spec]) ,或者如果它已被声明但未在其类定义中定义,则它可以在其类定义之外定义。出现在类定义之外的成员函数定义应出现在包含类定义的命名空间范围内......
2/ 内联成员函数(无论是静态的还是非静态的)也可以在其类定义之外定义,只要它在类定义中的声明或其在类定义之外的定义将该函数声明为 inline 或constexpr。 [ 注意: 命名空间范围内的类的成员函数具有该类的链接。本地类 ([class.local]) 的成员函数没有链接。请参阅 [basic.link]。 — 尾注 ]
所以基本上,成员函数没有在类定义中定义,也没有隐含地inline,因此“正常”规则适用,因此只能在程序中出现一次。
还有模板,它对 linkage 说了什么? §14/4 [temp]
模板名称具有链接([basic.link])。具有内部链接的模板的特化(显式或隐式)与其他翻译单元中的所有特化不同...模板定义应遵守单一定义规则([basic.def.odr])。