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

Posted

技术标签:

【中文标题】为啥模板类函数必须在同一个翻译单元中声明?【英文标题】:Why do template class functions have to be declared in the same translation unit?为什么模板类函数必须在同一个翻译单元中声明? 【发布时间】:2011-11-05 23:12:00 【问题描述】:

以这段代码为例:

/*
 * 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_ */

.

/*
 * 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;

.

/*
 * 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;

这就是我通常声明非模板类的方式。不幸的是,此代码导致错误../src/main.cpp:18: undefined reference to 'Foo&lt;int&gt;::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_ */

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

感谢您的建议!

【问题讨论】:

【参考方案1】:

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

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

template class Foo<int>;

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

【讨论】:

“您可以在 cpp 文件中包含模板类函数,并且您可以使用您将要使用的参数显式实例化它。” 这个语法是什么? (编辑,没关系,谢谢)【参考方案2】:

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

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

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

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

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

// "Implementation" file
template <typename T> void Foo::f()  /* stuff */ 

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

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

【讨论】:

【参考方案3】:

模板为meta programming。它们不会(直接)编译成目标代码。只是他们的结果。

【讨论】:

从来不知道这个。虽然我仍然想知道为什么它们必须在同一个翻译单元中声明。 同样的原因,你不能在一个文件中声明半个类,而在另一个文件中声明另一半类。将模板视为仅声明,编译器正在编写您的实现。 可能听起来很俗气,但您可以将模板视为“类固醇上的宏”。与宏不同,它们深度集成到核心语言中。但是就像宏模板在被编译器实例化之前实际上不会发出代码。 @seand 同意。我用的不多,但一直认为它们是带有类型检查的#define。 @Joe 我认为模板非常强大——只要它们没有被滥用和过度使用。我无法理解每隔一行都是模板的 C++ 代码:(【参考方案4】:

大多数编译器还不支持外部模板,这将允许您正在寻找的 cpp/h 类型分开。但是,您仍然可以将模板声明与类似于您想要的实现分开。将声明放在 .h 文件中,将实现放在单独的源文件中,使用您想要的任何扩展名(.i 和 .ipp 很流行),然后将 #include 源文件放在 .h 文件的底部。编译器看到一个翻译单元,然后你就得到了代码分离。

/*
* 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);
;

#include "foo.ipp"

#endif /* FOO_H_ */

.

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

#include <iostream>

template<typename Type>
void Foo<Type>::Bar(Type object)

    std::cout << object;

【讨论】:

以上是关于为啥模板类函数必须在同一个翻译单元中声明?的主要内容,如果未能解决你的问题,请参考以下文章

为啥结构必须与模板类在同一个命名空间中才能编译?

如何使用模板声明成员函数? (不是模板类)

类模板模板类函数模板模板函数

模板实现顺序表

类模板,链表,直接插入排序,选择排序,起泡排序

C++ 编译与翻译单元