【问题标题】:Do template class member function implementations always have to go in the header file in C++? [duplicate]模板类成员函数实现是否总是必须放在 C++ 的头文件中? [复制]
【发布时间】:2012-01-29 13:20:45
【问题描述】:

通常,当我创建一个类时,我会为该类创建一个标题和一个源。我听说使用模板类,您必须将函数实现放在标题中。我尝试了两种方式,第一种方式得到了编译错误。第二种方法效果很好。但是,我喜欢将我的代码组织到头文件和源文件中,那么是否可以将函数实现放入源文件中? (也许它需要特殊的编译标志或语法?)或者我应该将 em 保留在标题中?

谢谢!

【问题讨论】:

标签: c++ templates


【解决方案1】:

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

正如 Aaron 在下面所说,在特定情况下,可以将实现细节放在 .cpp-file 中,在这种情况下,您事先知道模板将被实例化的所有可能类型,并用这些类型显式实例化它。如果模板在代码中的某处使用另一种类型实例化,您将收到链接器错误。

至少在视觉上将接口与实现分开的一种非常常见的通用解决方案是将所有实现放在.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
}

【讨论】:

  • 一个常用的扩展名是.tcc(如“模板C++”)。
  • Boost 为此使用扩展名.ipp(匹配.hpp 用于标头,.cpp 用于源文件)。
  • 我相信你可以调用你的模板实现文件任何东西,只要它在项目中是一致的(并遵循公司的指导方针)。
  • 您并不总是需要将模板代码放在标题中(请参阅我的答案)。如果您正在制作一个可供其他人使用的库,那么您可能确实需要将代码放在标题中。但通常您将控制自己的项目,并且您将确切知道将要实例化哪些模板,并且您将能够在标头中实例化它们,将实现留给传统的 cpp 文件。
  • 查看这个问题的答案以获得更多示例,这个确切的问题之前已经被问过(并且回答正确!)stackoverflow.com/a/2351622/146041
【解决方案2】:

(已编辑:这是一个更健壮的版本,它允许将实现编译到单独的 .o 文件中。模板应该在实现 cpp 文件的末尾显式实例化。也许这个只是 g++ 的问题。)

您不需要将实现放在标题中如果您知道哪些模板将被实例化并可以在 header 实现文件中列出它们。例如,如果你知道你只会使用intstd::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&lt;int&gt;Test&lt;string&gt; 的完整实现,并且可以在应用程序的其余部分轻松链接。

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

小贴士:即使是公用的,也要看看方法,看看有没有参数和返回类型与模板参数无关的方法。如果是这样,您可以将它们作为(纯)虚函数放入基类中。此 Base 类不使用任何模板,因此您可以在您的大部分应用程序中使用此 Base*template &lt;class A&gt; class Test : public Base { ...,允许您限制模板代码在整个应用程序中的范围。我最近发现这很有用当一个类的大部分底层行为和构造依赖于模板参数时,但已构造对象的接口不依赖于模板参数。

【讨论】:

  • 不知道为什么这被否决了,这是一个合理的建议,即使限制使它仅在某些情况下可用。来自我的 +1。
【解决方案3】:

回答最初的问题:不,[member] 函数模板的定义不必进入标题。但是,编译器需要查看定义来实例化模板。对于使用许多不同类型实例化的模板,您希望模板在使用时被隐式实例化。这是例如像std::vector&lt;...&gt; 这样的类模板和像std::copy(...) 这样的函数模板的情况。在这种情况下,将模板定义与其声明分开几乎肯定是不切实际的,尽管我个人将定义放在头文件底部包含的单独文件中。

对于仅使用流类或std::basic_string&lt;...&gt; 等少数类型实例化的模板,通常最好在单独的类似头文件的文件中定义函数模板,该文件仅包含在显式实例化它们的实现文件中。这样,实例化模板的工作只花费一次,而不是在每个使用它的翻译单元中。尤其是对于流类,这会产生巨大 的差异(主要是编译和链接时间,但在某些系统上也适用于可执行文件大小)。 ...而且我很确定几乎没有人会费心使用具有不同字符类型的流类而不是 charwchar_t(提示:安排要实现的各个方面并非易事并出现在std::locale)。如果模板只能使用一组有限的类型,那么显式实例化模板的技术也很有效。

【讨论】:

    【解决方案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();
    };
    

    除非您有充分的理由不这样做,否则将所有内容都放在课堂上会容易得多。

    【讨论】:

    • 好点,但这与提出的问题无关。
    • @AntonyHatchkins 感谢您修正错字。 WRT 我的回答的相关性,一旦你确定某事是可能的,下一个值得考虑的问题通常是它是否有利。是的,可以使用模板将实现与接口分开,但由于调用代码通常依赖于实现,这样做几乎没有优势。我当时认为,并且仍然认为,解释为什么你不想将接口和实现分开会更有帮助。
    • 拜托,我没有对你投反对票 :) 谢谢你的回答!我的语气是因为问题中有一个“标题”一词。您的答案中没有“标题”一词。它制造了巨大的差异。这里的“技术上可能”有点过于乐观了。显式实例化所有可想象的(现有的和未来的)模板参数的负担比在列表增长时向模板列表添加一个额外的参数要糟糕得多。
    【解决方案5】:

    模板实现需要在编译时知道。这意味着实现必须对编译器可见。没有办法解决这个问题。

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

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

    【讨论】:

    • 在某些情况下,您可以将模板类成员函数的实现放在常规的 cpp 文件中。看我的回答。
    • @AaronMcDaid 这不是一个好主意。它违反了松散耦合。您不能将模板扩展到同一模块之外。
    • 首先,您同意可以对除一个 cpp 文件之外的所有文件隐藏实现吗?如果您知道将使用哪些模板,则不需要松散耦合。这取决于上下文,在某些情况下,开发人员知道哪些模板将被实例化,哪些不会。
    猜你喜欢
    • 2013-11-16
    • 1970-01-01
    • 2010-12-02
    • 2019-12-22
    • 1970-01-01
    • 2011-02-03
    • 1970-01-01
    • 2013-10-19
    • 2023-03-15
    相关资源
    最近更新 更多