为什么模板类函数必须在同一个翻译单元中声明?

Aut*_*tAM 6 c++ templates mingw compilation

以这段代码为例:

/*
 * foo.h
 *
 *  Created on: Nov 5, 2011
 *      Author: AutoBotAM
 */

#ifndef FOO_H_
#define FOO_H_

template<typename Type>
class Foo
{
public:
    void Bar(Type object);
};


#endif /* FOO_H_ */
Run Code Online (Sandbox Code Playgroud)

/*
 * foo.cpp
 *
 *  Created on: Nov 5, 2011
 *      Author: AutoBotAM
 */

#include <iostream>
using namespace std;

#include "foo.h"

template<typename Type>
void Foo<Type>::Bar(Type object)
{
    cout << object;
}
Run Code Online (Sandbox Code Playgroud)

/*
 * main.cpp
 *
 *  Created on: Oct 15, 2011
 *      Author: AutoBotAM
 */

#include <iostream>
using namespace std;

#include "foo.h"

Foo<int> foo;

int main()
{
    cout << "The year is ";
    foo.Bar(2011);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这就是我通常声明非模板类的方式。不幸的是,这段代码会导致错误../src/main.cpp:18: undefined reference to 'Foo<int>::Bar(int)'(在 MinGW 中)。我做了一些阅读,结果发现你必须在同一个翻译单元中声明模板类,如下所示:

/*
 * foo.h
 *
 *  Created on: Nov 5, 2011
 *      Author: AutoBotAM
 */

#ifndef FOO_H_
#define FOO_H_

template<typename Type>
class Foo
{
public:
    void Bar(Type object)
    {
        cout << object;
    }
};


#endif /* FOO_H_ */
Run Code Online (Sandbox Code Playgroud)

我的大问题是,为什么你必须这样做?我可以想象这个方案在开发过程中会遇到一些陷阱。例如,假设我们有 50 个翻译单元 #include foo.h,并且我们对 进行了更改void Foo::Bar(Type)。由于Bar位于头文件中,因此我们必须等待所有 50 个翻译单元编译完毕才能得到任何结果。如果我们单独Bar工作foo.cpp,我们只需要等待 1 个翻译单元编译即可。有什么方法可以克服这个问题吗?

感谢您的任何建议!

K-b*_*llo 5

模板位于编译和链接时间之间。在看到模板声明后,急切的实现可以做很多事情,但在使用模板参数实例化模板之前无法生成实际代码。

可以在 cpp 文件中包含模板类函数,并且可以使用将要使用它的参数显式实例化它。但是,您只能在整个程序中使用这些实例化。例如,您可以添加到foo.cpp

template class Foo<int>;
Run Code Online (Sandbox Code Playgroud)

Foo<int>现在,即使实现位于其自己的翻译单元中,您也可以在任何地方使用s 。但是,您不能使用任何其他类型,Foo<>因为链接器将无法找到其函数(它们实际上并不存在)。


Ker*_* SB 4

模板不是类型。它们只是模板。它们仅在实例化时才成为一种类型(具有完整的参数集)。

编译要求所有必要的类型定义在编译时可用。此外,链接要求所有必需的函数定义(包括成员函数)存在于某个翻译单元中(内联提供了对单一定义规则的通常豁免)。

如果将所有这些放在一起,几乎可以自动得出结论:模板类的所有模板成员函数定义必须可用于编译过程中某个时刻使用的每个模板实例化。

另一方面,请考虑以下设置:

// Header file:
template <typename T> struct Foo { void f(); }

// "Implementation" file
template <typename T> void Foo::f() { /* stuff */ }
Run Code Online (Sandbox Code Playgroud)

如果编译实现文件,它不包含任何代码,因为没有要编译的模板实例。头文件的用户可以实例化Foo<int>,但类的主体永远不会在任何 TU 中实例化,因此在拼凑程序时会出现链接器错误。

将模板视为代码生成工具而不是实际代码可能会有所帮助,至少出于编译的目的。