模板类成员函数实现总是必须放在C ++的头文件中吗?

Do template class member function implementations always have to go in the header file in C++?

本问题已经有最佳答案,请猛点这里访问。

通常,当我创建一个类时,我会为该类创建一个头和一个源。我听说,对于模板类,必须将函数实现放在头中。我尝试了两种方法,第一种方法是编译错误。第二种方法很管用。但是,我喜欢将代码组织到头文件和源文件中,所以是否可以将函数实现放入源文件中?(可能需要特殊的编译标志或语法?)或者我应该把它们放在头上吗?

谢谢!


通常,所有模板代码都必须在头文件中,因为编译器需要知道实例化点的完整类型。

正如Aaron在下面所说,在特定情况下,可以将实现细节放在.cpp文件中,在这种情况下,您可以事先知道模板将用这些类型实例化的所有可能类型,并用这些类型显式实例化模板。然后,如果在代码中的某个地方用另一个类型实例化模板,您将得到一个链接器错误。

对于至少在视觉上与实现分离的接口,一个非常常见的一般解决方案是将所有实现放在一个.inc文件(或.tcc.ipp文件)中,并将其包含在头文件的末尾。

请注意,将模板类成员放在类定义之外的语法(无论是使用特定的解决方案还是常规的解决方案)有点麻烦。你需要写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// in test.h
template <class A>
class Test
{
public:
  void testFunction();
};

#include"test.inc"

// in test.inc    
template <class A>
void Test<A>::testFunction()
{
  // do something
}


(已编辑:这是一个略为强大的版本,允许将实现编译成单独的.o文件。模板应该在实现cpp文件的末尾显式实例化。也许这只是G++的问题。)

如果您知道将实例化哪些模板,并且可以将它们列在header->strike>implementation文件中,则不需要将实现放在头中。例如,如果您知道只使用intstd::string,则可以将其放入头文件:

1
2
3
4
5
6
7
// test.h
template <class A>
class Test
{
public:
  void f();
};

并将f()的实现放入普通test.cpp文件中:

1
2
3
4
5
6
7
// test.cpp
#include"test.h"
template <class A> void Test<A>::f() {
   // implementation
}
template class Test<int>;
template class Test<string>;

最后两行显式地实例化模板类。在看到成员函数的实现之后,最好将它放在实现文件的末尾。然后您可以将它编译成.o文件g++ -c test.cpp。这个test.o文件将包含TestTest的完整实现,并且可以在应用程序的其余部分中轻松链接。

这是可行的,但这是个好主意吗?这取决于上下文。在许多情况下,这是非常有效的。如果您正在为项目中的"内部"使用编写模板,那么您知道哪些模板将被实例化,而哪些模板将不被实例化。但是,如果您将一些必须非常灵活的东西提供给公众,那么您将需要在头文件中包含实现。

提示:即使是用于公共消费,也要查看这些方法,看看是否有任何方法的参数和返回类型与模板参数无关。如果是这样,您可以将它们作为(纯)虚拟函数放到基类中。这个基类不使用任何模板,因此您可以在大部分应用程序(template class Test : public Base { ...中使用这个Base*,从而可以限制模板代码在整个应用程序中的访问。最近我发现,当类的许多底层行为和构造依赖于模板参数,但到已经构造的对象的接口不依赖于模板参数时,这一点很有用。


要回答最初的问题:不,不必将[成员]函数模板的定义放在头中。但是,编译器需要查看定义来实例化模板。对于用许多不同类型实例化的模板,您希望在使用模板时将其隐式实例化。例如,对于类模板(如EDOCX1)(0)和函数模板(如EDOCX1)(1))。在这种情况下,将模板定义从声明中分离出来几乎肯定是不切实际的,尽管我个人将这些定义放在包含在头文件底部的单独文件中。

对于仅使用流类或std::basic_string<...>等少数类型实例化的模板,通常最好在单独的类头文件中定义函数模板,该文件仅包含在显式实例化它们的实现文件中。这样,实例化模板的工作只花费一次,而不是在使用模板的每个翻译单元中。特别是对于流类,这会产生巨大的差异(主要是对于编译和链接时间,但在某些系统上,也对于可执行的大小)。…我敢肯定,几乎没有人会遇到使用不同字符类型的流类的麻烦,而不是使用charwchar_t(提示:安排std::locale中要实现和呈现的各个方面是很重要的)。如果模板要使用的类型集有限,显式实例化模板的技术也可以很好地工作。


虽然从技术上讲,您可以将实现与接口分离,但模板的语法对于重复键入非常烦人,我强烈建议您先别着急,然后将实现放在类中,直到消除异味。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <class X>
class klass_t {
public:
    void f();
    void g();
    void h();
};

template <class X>
void klass_t<X>::f() { ... }

template <class X>
void klass_t<X>::g() { ... }

template <class X>
void klasS_t<X>::h() { ... }

本来应该是:

1
2
3
4
5
6
7
template <class X>
class klass_t {
public:
    void f() { ... }
    void g() { ... }
    void h() { ... }
};

现在假设您想要添加另一个模板参数(如下所示)。现在您必须在n个位置更改模板参数列表,而不仅仅是一个。

1
2
3
4
5
6
7
template <class X, class Y>
class klass_t {
public:
    void f();
    void g();
    void h();
};

除非你有充分的理由不这么做,否则把所有的东西都放到课堂上就容易多了。


模板实现需要在编译时知道。这意味着编译器必须能够看到这些实现。没有办法解决这个问题。

如果您想对实现细节保密,就没有办法做到这一点。当然,您可以混淆您的代码,但这并不是什么障碍。

如果您唯一关心的是代码组织,那么可以用实现创建一个单独的include文件,并将其包含在主标题的末尾(就像andreas的建议一样)。