C++ 为什么不重新定义模板,为什么都写在头文件中?

C++ 为什么不重新定义模板,为什么都写在头文件中?,c++,templates,c++11,linker,C++,Templates,C++11,Linker,如果我写下这样的话: // 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 #inclu

如果我写下这样的话:

// 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 u thiscall A::f(void)”(?f@A@@QAEXZ) 已在B.obj中定义

A
类是类模板时,为什么不发生相同的问题?在编译过程中,它最终会变成一个普通类(非模板类),对吗?因此,我希望非模板类的行为与非模板类相同,即链接器错误。

为什么在涉及多个定义时,非模板函数的处理方式与模板不同

这里涉及到历史和兼容性问题。有些需求来自C,这就是它的工作方式。还有一些与模板相关的原因,它们是代码生成器;当需要时,编译器需要生成代码,因此需要在生成代码时查看代码。这会产生连锁反应,会有多个定义,因此需要规则来解决这些问题

简而言之;模板的行为(w.r.t.链接)就像它们在程序中有一个单一的定义一样,因此它们在编译和链接过程中的行为与非模板(未使用
内联
声明)不同,尤其是w.r.t.函数。如果将非模板声明为
内联
,则会看到类似的行为


这里的标准参考包括:

一些背景,这里的大多数问题都与链接有关,什么是链接

当一个名称可能表示与另一个作用域中的声明引入的名称相同的对象、引用、函数、类型、模板、命名空间或值时,称该名称具有链接:

  • 当名称具有外部链接时,它所表示的实体可以通过来自其他翻译单元的范围或来自同一翻译单元的其他范围的名称来引用
  • 当名称具有内部链接时,它所表示的实体可以由同一翻译单元中其他作用域的名称引用
  • 当一个名称没有链接时,它所表示的实体不能被其他作用域中的名称引用
关于函数和变量的一些一般规则,适用于整个程序和每个翻译单元

任何翻译单位不得包含一个以上的定义 变量、函数、类类型、枚举类型或模板

每个程序应包含该程序中使用的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
的定义不满足这些要求,则行为未定义

上面列表中的一些非正式观察,包括类、模板等。这些是头文件中常见的典型元素(当然不限于头文件)。他们被赋予了这些特殊的规则,以使一切按预期进行

类成员函数呢

1/成员函数可以在其类定义中定义([dcl.fct.def]),在这种情况下,它是一个内联成员函数([dcl.fct.spec]),也可以在其类定义之外定义(如果它已经声明但未在其类定义中定义)。出现在类定义之外的成员函数定义应出现在包含类定义的命名空间范围内

2/内联成员函数(无论是静态的还是非静态的)也可以在其类定义之外定义,只要其在类定义中的声明或在类定义之外的定义将函数声明为
inline
constepr
。[注意:命名空间范围中的类的成员函数具有该类的链接。本地类([class.local])的成员函数没有链接。请参阅[basic.link]。-结束注意]

因此,基本上,成员函数没有在类定义中定义,也没有隐式地
内联
,因此“正常”规则适用,因此只能在程序中出现一次

和模板,它说什么关于链接

模板名称具有链接([basic.link])。具有内部链接的模板的专门化(显式或隐式)不同于其他翻译单元中的所有专门化。。。模板定义应遵守一个定义规则([basic.def.odr])


模板不是代码;它们是用于创建代码的模式。它们必须在任何地方都可见,因此编译器必须有使用它们的特殊规则。这里的关键特殊规则是,编译器在使用模板的任何地方生成代码,链接器忽略重复项。

这里有两种不同的效果:

  • 如果成员函数定义不一致,则为否
    struct Foo {
       void bar() {}   // "inline" implied
    };