8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

为什么模板只能在头文件中实现?

Jiren 2月前

129 0

摘自《C++ 标准库:教程和手册》:目前使用模板的唯一可移植方法是使用内联函数在头文件中实现它们。这是为什么?(

摘自 《C++ 标准库:教程和手册 :

目前使用模板的唯一可移植方法是使用内联函数在头文件中实现它们。

这是为什么呢?

(澄清:头文件不是 唯一的 可移植解决方案。但它们是最方便的可移植解决方案。)

帖子版权声明 1、本帖标题:为什么模板只能在头文件中实现?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Jiren在本站《class》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 314 2月前 0 只看Ta
    引用 2

    虽然将所有模板函数定义放入头文件中可能是最方便的使用方法,但仍然不清楚该引文中的“inline”是什么意思。没有必要为此使用内联函数。“Inline”与此完全无关。

  • 模板不像函数那样可以编译成字节码。它只是生成此类函数的一种模式。如果将模板单独放入 *.cpp 文件中,则无需编译。此外,显式实例化实际上不是模板,而是从模板中生成函数的起点,该函数最终位于 *.obj 文件中。

  • @AnT 或许他们的意思不是将“inline”作为关键字,而是将“在类内部声明的位置实现的方法”。

  • @palapapa:这听起来像是另一个问题(或者可能是两个,如果不是“这什么时候重要?”而是“这是什么意思?”)。

  • 警告:没有 必要 将实现放在头文件中,请参阅本答案末尾的替代解决方案。

    无论如何,代码失败的原因是,在实例化模板时,编译器会使用给定的模板参数创建一个新类。例如:

    template<typename T>
    struct Foo
    {
        T bar;
        void doSomething(T param) {/* do stuff using T */}
    };
    
    // somewhere in a .cpp
    Foo<int> f; 
    

    当读取此行时,编译器将创建一个新类(我们称之为 FooInt ),它相当于以下内容:

    struct FooInt
    {
        int bar;
        void doSomething(int param) {/* do stuff using int */}
    };
    

    因此,编译器需要访问方法的实现,才能使用模板参数(在本例中为 int )实例化它们。如果这些实现不在标头中,则无法访问它们,因此编译器将无法实例化模板。

    解决此问题的常见方法是将模板声明写在头文件中,然后在实现文件(例如 .tpp)中实现该类,并在头文件的末尾包含该实现文件。

    Foo.h

    template <typename T>
    struct Foo
    {
        void doSomething(T param);
    };
    
    #include "Foo.tpp"
    

    Foo.tpp

    template <typename T>
    void Foo<T>::doSomething(T param)
    {
        //implementation
    }
    

    这样,实现仍然与声明分离,但编译器可以访问。

    替代解决方案

    另一个解决方案是保持实现分离,并明确实例化您需要的所有模板实例:

    Foo.h

    // no implementation
    template <typename T> struct Foo { ... };
    

    Foo.cpp

    // implementation of Foo's methods
    
    // explicit instantiations
    template class Foo<int>;
    template class Foo<float>;
    // You will only be able to use Foo with int or float
    

    如果我的解释不够清楚,你可以看看关于 这个主题的 C++ 超级常见问题解答 .

  • 实际上,显式实例需要在能够访问 Foo 的所有成员函数的定义的 .cpp 文件中,而不是在标头中。

  • \'编译器需要访问方法的实现,才能使用模板参数(在本例中为 int)实例化它们。如果这些实现不在标头中,则无法访问它们\' 但为什么 .cpp 文件中的实现无法被编译器访问?编译器还可以访问 .cpp 信息,否则它如何将它们转换为 .obj 文件?编辑:此问题的答案在此答案提供的链接中...

  • 我认为这并没有清楚地解释这个问题,关键显然与编译单元有关,而这篇文章中没有提到这一点

  • @Gabson:结构和类是等价的,不同之处在于类的默认访问修饰符是“private”,而结构的默认访问修饰符是“public”。通过查看这个问题,您可以了解到其他一些细微的差异。

  • 我在本回答的最开始添加了一句话,以澄清这个问题是基于一个错误的前提。如果有人问“为什么 X 是真的?”而事实上 X 不是真的,我们应该迅速拒绝这个假设。

  • 这是因为需要单独编译,并且模板是实例化风格的多态性。

    让我们更具体一点来解释一下。假设我有以下文件:

    • foo.h
      • declares the interface of class MyClass<T>
    • foo.cpp
      • defines the implementation of class MyClass<T>
    • 酒吧.cpp
      • uses MyClass<int>

    单独编译意味着我应该能够 独立于 foo.cpp bar.cpp 。编译器完全独立地完成每个编译单元的分析、优化和代码生成的所有艰苦工作;我们不需要进行整个程序分析。只有链接器需要一次处理整个程序,而链接器的工作要容易得多。

    甚至不需要存在 bar.cpp 情况下分发到其他地方 ,但我仍然应该能够将 我已经拥有的 foo.o bar.o ,而无需重新编译 几年后他们编写的代码链接在一起 . foo.cpp foo.cpp 的 foo.cpp foo.cpp foo.cpp .

    \'实例化样式多态性\' 意味着模板 MyClass<T> 实际上并不是一个可以编译为适用于任何值的代码的通用类 T 。这会增加开销,例如装箱、需要将函数指针传递给分配器和构造函数等。C++ 模板的目的是避免必须编写几乎相同的 class MyClass_int , class MyClass_float 等,但仍然能够得到编译后的代码,就像我们 编写 了每个版本一样。因此,模板 实际上 一个模板;类模板不是 一个 类,它是为我们遇到的每个类创建新类的方法 T 。模板不能编译成代码,只能编译实例化模板的结果。

    因此,当 编译 foo.cpp bar.cpp 因此无法知道 MyClass<int> 需要什么。它可以看到模板 MyClass<T> ,但无法发出代码(它是模板,而不是类)。并且,当 bar.cpp ,编译器可以看到它需要创建一个 MyClass<int> ,但它看不到模板 MyClass<T> (只能看到它在 foo.h ),因此无法创建它。

    如果 时将生成代码 本身使用 MyClass<int> 想要提供的 foo.cpp ,因此当 bar.o 链接到 foo.o 无法将 bar.cpp 模板用作 模板 foo.cpp foo.cpp 模板类的预先存在的版本

    您可能认为,在编译模板时,编译器应该“生成所有版本”,而链接期间从未使用过的版本将被过滤掉。除了这种方法将面临的巨大开销和极端困难之外,因为“类型修饰符”功能(如指针和数组)甚至允许内置类型产生无限数量的类型,当我现在通过添加以下内容来扩展我的程序时会发生什么:

    • 巴兹.cpp
      • declares and implements class BazPrivate , and uses MyClass<BazPrivate>

    除非我们

    1. 每次我们修改 程序中的任何其他文件 都必须重新编译 foo.cpp ,以防它添加了一个新的实例 MyClass<T>
    2. 要求 baz.cpp 包含(可能通过标头包含)的完整模板 MyClass<T> ,以便编译器可以 MyClass<BazPrivate> 在编译 baz.cpp .

    没有人喜欢 (1),因为全程序分析编译系统需要 很长时间 才能编译完成,并且它使得在没有源代码的情况下无法分发编译后的库。所以我们改用 (2)。

  • 引用 13

    强调引用模板实际上是一个模板;类模板不是一个类,它是为我们遇到的每个 T 创建一个新类的方法

  • @Birger 您应该能够从任何有权访问完整模板实现的文件中执行此操作(因为它在同一个文件中或通过标头包含)。

  • 引用 15

    @ajeh 这不是修辞。问题是“为什么必须在标头中实现模板?”,所以我解释了 C++ 语言做出的导致这一要求的技术选择。在我写下答案之前,其他人已经提供了不是完整解决方案的解决方法,因为不可能有完整的解决方案。我觉得这些答案将通过对问题的“为什么”角度的更全面讨论来补充。

  • 请各位这样想象一下……如果您不使用模板(以便高效地编写所需内容),那么您无论如何都只能提供该类的几个版本。因此,您有 3 个选择。1)。不要使用模板。(与所有其他类/函数一样,没有人关心其他人不能改变类型)2)。使用模板,并记录他们可以使用的类型。3)。为他们提供整个实现(源)奖励 4)。为他们提供整个源代码,以防他们想从您的另一个类中创建模板 ;)

  • @VoB 是的,.tpp 文件在这种意义上只是一种头文件的命名约定。\'头文件\' 不是 C++ 编译器特有的东西,它只是我们所谓的文件,我们打算通过使用 #include 将其包含到其他编译单元中。如果它可以帮助您处理代码,将模板实现放在与描述 .cpp 文件接口的文件不同的文件中,并为这些模板实现文件提供特定的扩展名,如 .tpp,那就去做吧!编译器不知道也不关心其中的区别,但它可以帮助人类。

  • ld8 2月前 0 只看Ta
    引用 18

    这里有很多正确答案,但我想补充一点(为了完整性):

    如果您在实现 cpp 文件的底部明确实例化模板将使用的所有类型,则链接器将能够像往常一样找到它们。

    编辑:添加显式模板实例化的示例。在定义模板并定义所有成员函数后使用。

    template class vector<int>;
    

    这将实例化(从而使链接器可用)类及其所有成员函数(仅)。类似的语法适用于函数模板,因此如果您有非成员运算符重载,您可能需要对它们执行相同的操作。

    上面的例子相当没用,因为 vector 在头文件中是完全定义的,除非使用公共包含文件(预编译头文件?) extern template class vector<int> 来防止它在所有其他1000?)使用 vector 的文件中实例化它。

  • 引用 19

    呃。答案不错,但没有真正干净的解决方案。列出模板的所有可能类型似乎与模板的本意不符。

  • 引用 20

    这在很多情况下都是好的,但通常会破坏模板的目的,模板的目的在于允许您使用任何类型的类而无需手动列出它们。

返回
作者最近主题: