为什么模板类的实现和声明应该在同一个头文件中?

43
为什么模板类的实现和声明应该在同一个头文件中?你能通过举例来解释一下吗?

7
它们必须在同一编译单元内可见。如果两者都被包含,它们可以在不同的文件中。 - Anycorn
2
将声明和实现分为两个文件并不罕见。头文件通常会包含实现文件。 - MSalters
@MSalters:这在模板类中是有问题的,而这正是这个问题所涉及的。我希望我能够对评论进行反对投票。 - Lightness Races in Orbit
4
@Tomalak Geret'kal: 完全不是这样的。在 "Foo.h" 中有一个声明,在 "Foo.impl" 中有定义。"foo.h" 在头文件保护之前的最后一行是 "#include "Foo.impl""。预处理器运行后,它们没有任何区别。它们可能在不同的文件中,但它们最终在相同的翻译单元中 - 这对编译器很重要。 - MSalters
@MSalters:好的,我明白了。实际上,我完全把你最后一句话读反了。 :) - Lightness Races in Orbit
4个回答

26
编译器需要访问整个模板定义(而不仅仅是签名),才能为每个模板实例生成代码,因此您需要将函数的定义移动到头文件中。
要了解更多详情,请阅读有关包含模型的内容。

1
不需要,你可以在每个TU中手动编写定义,但是头文件约定当然是为了帮助你避免这种无聊的事情。 :) - Lightness Races in Orbit
@LightnessRacesinOrbit,我可以问一下TU是什么吗? - athos
@athos 请参考:http://zh.wikipedia.org/wiki/%E7%BF%BB%E8%AF%91%E5%8D%95%E5%85%83%E6%A8%A1%E5%9D%97 - Lightness Races in Orbit
@LightnessRacesinOrbit,明白了,谢谢! - athos

5
类模板的定义和其成员函数的实现必须对每个实例化它的地方都可见,且必须使用不同的类型。例如,为了实例化myTemplate<int>,你需要看到myTemplate的完整定义和实现。
最简单的方法是将模板的定义和其成员函数放在同一个头文件中,但也有其他方法。例如,你可以将成员函数的实现放在一个单独的文件中,并将其单独包含。然后你可以从第一个头文件中包含它,或者只在需要时包含实现文件。
例如,一种常见做法是在一个.cpp文件中显式实例化不同参数的模板,然后在头文件中声明这些实例化为extern。这样,那些实例化可以在其他源文件中使用,而不要求模板成员函数的实现可见。但是,除非你包含实现文件,否则你将无法使用其他模板参数集合。
例如,如果你定义了myTemplate<int>myTemplate<std::string>extern,那么你可以正常使用它们,但如果没有定义myTemplate<double>extern,那么你就无法使用它而不需要实现。

4

他们不必这样做。

必要的是模板定义在实例化点(使用处)可见,这样编译器就能在此处从模板推导出类/函数。

然而,对于模板类,使用两个头文件是非常常见的:

// foo_fwd.hpp
template <typename T, typename U> struct Foo;

// foo.hpp
#include "foo_fwd.hpp"

template <typename T, typename U> struct Foo { typedef std::pair<T,U> type; };

这样可以让那些不需要完整模板定义的人包含一个较轻的标题,例如:
//is_foo.hpp
#include <boost/mpl/bool.hpp>
#include "foo_fwd.hpp"

template <typename Z>
struct is_foo: boost::mpl::false_ {};

template <typename T, typename U>
struct is_foo< Foo<T,U> >: boost::mpl::true_ {};

这可以稍微加快编译时间。


4

对于普通类,只有声明足以进行编译,并且相应的定义将被链接。

对于模板而言,编译器还需要定义来生成代码。

这种差异在C++ FAQ中有更好的解释。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接