通常,当我创建一个类时,我会为该类创建一个标头和一个源。我听说过使用模板类,你必须将函数实现放在头文件中。我试过两种方式,并从第一种方式得到编译错误。第二种方式很好。但是,我喜欢将代码组织到头文件和源文件中,那么是否可以将函数实现放入源文件中? (也许它需要特殊的编译标志或语法?)或者我应该将em保留在标题中?
谢谢!
答案 0 :(得分:15)
通常,所有模板代码都必须位于头文件中,因为编译器需要在实例化时知道完整类型。
正如Aaron所说,可以将实现细节放在.cpp
- 文件中,在特定情况下,您事先知道模板将被实例化的所有可能类型,并使用这些类型显式实例化它。如果模板在代码中的某个位置使用其他类型实例化,则会出现链接器错误。
至少在视觉上将界面与实现区分开来的一个非常常见的一般解决方案是将所有实现放在.inc
(或.tcc
或.ipp
)文件中,并将其包含在最后头文件。
请注意,将模板类成员放在类定义之外的语法(无论是使用特定解决方案还是常规解决方案)都有点麻烦。你需要写:
// 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
}
答案 1 :(得分:14)
(已编辑:这是一个稍微强大的版本,允许将实现编译为单独的.o文件。模板应在实现cpp文件的末尾显式实例化。也许这只是g ++的一个问题。)
如果知道哪些模板将被实例化,并且可以在 header 实现文件中列出它们,则不需要将实现放在标头中。例如,如果您知道只使用int
和std::string
,那么您可以将其放在头文件中:
// test.h
template <class A>
class Test
{
public:
void f();
};
并将f()的实现放入一个普通的test.cpp文件中:
// 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文件将包含Test<int>
和Test<string>
的完整实现,并且可以在应用程序的其余部分中轻松链接。
它有效,但这是个好主意吗?这取决于背景。在许多情况下,这非常有效。如果您正在为项目中的“内部”使用编写模板,那么您将知道将实例化哪些模板,哪些模板不会实例化。但是,如果您正在向公众提供必须非常灵活的内容,那么将需要将实现包含在头文件中。
提示:即使是供公众使用,也请查看方法,看看是否存在参数和返回类型独立于模板参数的方法。如果是这样,您可以将它们作为(纯)虚函数放入Base类。此Base类不使用任何模板,因此您可以在应用程序的大部分Base*
中使用此template <class A> class Test : public Base { ...
,从而允许您在整个应用程序中限制模板化代码的范围。最近发现这很有用,因为类的许多基础行为和构造都依赖于模板参数,但接口到已经构造的对象并不依赖于模板参数。
答案 2 :(得分:5)
回答原始问题:不,[member]功能模板的定义不必进入标题。但是,编译器需要查看定义以实例化模板。对于使用许多不同类型实例化的模板,您希望模板在使用时隐式实例化。这是例如类似std::vector<...>
等类模板以及std::copy(...)
等函数模板的情况。在这种情况下,将模板定义与其声明分开几乎肯定是不切实际的,尽管我个人将这些定义放在头文件底部的单独文件中。
对于仅使用几种类型(例如流类或std::basic_string<...>
实例化的模板,通常最好在单独的类似头文件中定义函数模板,该文件仅包含在显式实例化它们的实现文件中。这样,实例化模板的工作仅花费一次而不是在使用它的每个翻译单元中。特别是对于流类,这会产生巨大的差异(主要用于编译和链接时间,但在某些系统上也用于可执行文件大小)。 ...而且我很确定几乎没有人遇到过使用不同于char
和wchar_t
的字符类型的流类的麻烦(提示:安排各种不同的字符串是非常重要的)要实现并出现在std::locale
中的方面。如果只有一组有限的类型可以使用模板,那么显式实例化模板的技术也很有效。
答案 3 :(得分:4)
虽然从技术上讲你可以将你的实现与你的界面分开,但是模板的语法很难反复输入我强烈建议你只是捏着鼻子把你的实现放在你的课堂上,直到你克服了气味
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() { ... }
本来会:
template <class X>
class klass_t {
public:
void f() { ... }
void g() { ... }
void h() { ... }
};
现在假设您要添加另一个模板参数(如下所示)。 现在您必须在n个位置更改模板参数列表,而不仅仅是一个。
template <class X, class Y>
class klass_t {
public:
void f();
void g();
void h();
};
除非你有充分的理由不这样做,否则只需把所有东西都放在课堂上就可以轻松了。
答案 4 :(得分:2)
模板实现需要在编译时知道。这意味着编译器可以看到必须的实现。没有办法解决这个问题。
如果您希望将实施细节保密,则无法执行此操作。你当然可以模糊你的代码,但这并不是一个障碍。
如果你唯一关心的是代码组织,你可以创建一个单独的包含文件,并将其包含在主标题的末尾(就像安德烈亚斯的建议一样)。