如果我写这样的话:
// A.h
#ifndef A_h
#define A_h
class A
{
public:
void f();
};
void A::f()
{
}
#endif //A_h
// B.cpp
#include "A.h"
void foo()
{
A a;
a.f();
}
// C.cpp
#include "A.h"
void bar()
{
A b;
b.f();
}
// main.cpp
#include "B.cpp"
#include "C.cpp"
using namespace std;
int main()
{
foo();
bar();
return 0;
}
我收到链接器错误:
错误LNK2005:“public:void __thiscall A :: f(void)”(?f @ A @@ QAEXXZ) 已在B.obj中定义
为什么A
类是类模板时不会发生同样的问题?最终它在编译期间变成了普通类(非模板类),对吧?出于这个原因,我期望与非模板类相同的行为,即链接器错误。
答案 0 :(得分:5)
这里有两个独立的效果:
一个成员函数定义是一个正常的函数定义,并且通过一个定义规则(ODR),它必须在链接中恰好出现一次。内联定义的成员函数隐式inline
,ODR允许重复内联函数定义:
也就是说,可以将以下代码放在标题中并重复包含它:
struct Foo {
void bar() {} // "inline" implied
};
但如果您的定义不符合规定,则必须在单个翻译单元中。
功能模板可以重复定义,即使它们不是内联的。模板机制通常需要处理模板的重复实例化,以及链接时的重复数据删除。
类模板的成员函数本身就是函数模板,因此将它们声明为inline
并不重要。
答案 1 :(得分:2)
为什么在涉及多个定义时,非模板函数对模板的处理方式不同?
此处涉及历史和兼容性问题。一些要求来自C,这就是它的工作方式。还有与模板有关的原因,它们是代码生成器;在需要时,编译器需要生成代码,因此需要在生成代码时查看代码。这会产生多种定义,因此需要使用规则来解决这些问题。
简单地说;模板行为(w.r.t.链接)好像它们在程序中有一个单一的定义,因此它们在编译和链接期间的行为与非模板(未用inline
声明)相同 - 特别是w.r.t.功能。如果声明了非模板inline
,则会看到类似的行为。
此处的标准参考包括;
有些背景,这里的大部分问题都与链接有关,什么是链接? §3.5/2 [basic.link]
当一个名称可能表示与另一个范围内的声明引入的名称相同的对象,引用,函数,类型,模板,名称空间或值时,该名称具有链接:
- 当名称具有外部链接时,其表示的实体可以通过其他翻译单位的范围或同一翻译单位的其他范围中的名称来引用。
- 当名称具有内部链接时,其表示的实体可以通过同一翻译单元中其他范围的名称来引用。
- 如果名称中没有无链接,则表示的实体不能通过其他范围的名称引用。
与函数和变量相关的一些一般规则,用于整个程序和每个翻译单元。
任何翻译单位不得包含任何一个以上的定义 变量,函数,类类型,枚举类型或模板。
和
每个程序应该只包含该程序中使用的每个非内联函数或变量的一个定义......
可以有多个类类型的定义(Clause [class]),枚举类型([dcl.enum]),带外部链接的内联函数([dcl.fct.spec]),类模板(Clause) [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 /内联成员函数(无论是静态还是非静态)也可以在其类定义之外定义,前提是它在类定义中的声明或在类定义之外的定义将函数声明为{{1} }或
inline
。 [注意:命名空间范围内的类的成员函数具有该类的链接。本地类的成员函数([class.local])没有链接。见[basic.link]。 - 结束记录]
基本上,成员函数没有在类定义中定义,也没有隐式constexpr
,因此"正常"规则适用,因此只能在程序中出现一次。
模板,它对链接有什么看法? §14/4 [temp]
模板名称具有链接([basic.link])。具有内部链接的模板的特化(显式或隐式)与其他翻译单元中的所有特化不同...模板定义应遵循单定义规则([basic.def.odr])。
答案 2 :(得分:0)
模板不是代码;它们是用于创建代码的模式。它们必须在任何地方都可见,因此编译器必须具有使用它们的特殊规则。这里的关键特殊规则是编译器在使用模板的任何地方生成代码,并且链接器忽略重复项。