【问题标题】:Why use a "tpp" file when implementing templated functions and classes defined in a header?为什么在实现头文件中定义的模板化函数和类时使用“tpp”文件?
【发布时间】:2017-06-27 06:59:16
【问题描述】:

请参考this question about implementing templates.的第一个答案

具体来说,请注意这句话

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

我将我最感兴趣的部分加粗。
.tpp 文件有什么意义?我尝试完全按照该页面中的建议进行操作,并且成功了。但是后来,我将文件扩展名更改为任何随机乱码(如 .zz 或 .ypp),它仍然有效!它应该工作吗?它是 .tpp 还是任何其他扩展名是否重要?为什么不使用 .cpp?

还有一件事让我感到困惑。
如果我的实现是用 .cpp 编写的,并且头文件定义了非模板函数,那么我只需要编译 .cpp 文件一次,对吗?至少在我更改 .cpp 文件中的某些内容之前。

但是,如果我有一个定义模板函数的标头,并且我的实现在一个带有随机时髦扩展名的文件中,它是如何编译的? 每次我编译任何源代码#includes 所说的标头时都会编译实现吗?

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    它是 .tpp 还是任何其他扩展名是否重要?为什么不使用 .cpp?

    扩展名是什么无关紧要,但不要使用.cpp,因为它违反了约定(它仍然可以工作,但不要这样做;.cpp 文件通常是源文件)。除此之外,这取决于您的代码库使用什么。例如,我(和 Boost 代码库)为此目的使用 .ipp

    .tpp 文件有什么意义?

    当您不希望包含模块接口的文件包含所有血腥的实现细节时使用它。但是您不能在.cpp 文件中编写实现,因为它是一个模板。所以你尽你所能(不考虑显式实例化等)。例如

    Something.hpp

    #pragma once
    
    namespace space {
    
    template <typename Type>
    class Something {
    public:
        void some_interface();
    };
    
    } // namespace space
    
    #include "Something.ipp"
    

    Something.ipp

    #pragma once
    
    namespace space {
    
    template <typename Type>
    void Something<Type>::some_interface() {
        // the implementation
    }
    
    } // namespace space
    

    我认为在头文件中编写定义和在单独的文件中编写实现的全部目的是节省编译时间,这样您只需编译一次实现,直到您进行一些更改

    您不能将通用模板代码拆分为实现文件。您需要完整的代码可见才能使用模板,这就是为什么您需要将所有内容放在头文件中的原因。更多内容见Why can templates only be implemented in the header file?

    但是,如果实现文件有一些看起来很时髦的文件扩展名,那么它在编译方面是如何工作的?它是否像实现在 cpp 中一样高效?

    您不编译 .tpp.ipp-inl.h 等文件。它们就像头文件一样,只是它们只包含在其他头文件中。您只编译源文件(.cpp.cc)。

    【讨论】:

    • 所以,为了确保我在关注,在标题中写入类似#include "A.tpp" 的内容,其中A.tpp 是带有标题实现的文件,相当于简单地在头文件本身?
    • @Manuel 完全一样,本质上是将该文件复制粘贴到当前文件中
    • 但这一定意味着每次在另一个文件中使用时都会编译实现代码,对吧?如果头文件定义了常规的非模板函数,那么它们的实现只需要编译一次。有没有办法用模板函数做到这一点?
    • @Manuel 有这个en.wikipedia.org/wiki/Precompiled_header,但我从来没有亲自使用过它。除此之外,没有办法用模板函数来做到这一点。在某些情况下,您可以摆脱这样的事情,例如使用std::string(显式实例化)然后编译源代码,请参阅stackoverflow.com/questions/4933056/…
    • 我明白了!非常感谢,@Curious。我想现在我终于明白了。当然,我非常感谢所有回答我问题的人。
    【解决方案2】:

    文件扩展名对预处理器没有意义; .h 也没有什么神圣之处。这只是约定,因此其他程序员知道并理解文件包含的内容。

    预处理器将允许您将任何文件包含到任何翻译单元中(这是一个非常生硬的工具)。像这样的扩展只是有助于阐明应该在哪里包含什么。

    【讨论】:

      【解决方案3】:

      它是 .tpp 还是任何其他扩展名是否重要?为什么不使用 .cpp?

      实际使用哪个扩展并不重要,只要它不同于用于 C++ 翻译单元的任何标准扩展即可。

      原因是具有不同的文件扩展名,因为它们通常被任何 C++ 构建系统检测到用于翻译单元(.cpp.cc、...)。因为将它们翻译为源文件会失败。它们必须是包含模板声明的相应头文件的#included。

      但是如果实现文件有一些看起来很时髦的文件扩展名,那么它在编译方面是如何工作的?

      必须是#included 才能如前所述进行编译。

      它是否像实现在 cpp 中一样高效?

      嗯,就编译时间而言,效率不如从翻译单元生成的纯目标文件那样 100%。它将再次编译,只要包含 #include 语句的标头发生更改。

      每次我编译任何源代码#includes 所说的标头时,是否都会编译实现?

      是的,他们是。

      【讨论】:

        【解决方案4】:

        头文件的文件扩展名在 C++ 中无关紧要,尽管应避免使用标准的源文件扩展名,例如 .cpp

        但是,有既定的约定。这些帮助人类程序员浏览代码。调用模板实现文件.tpp 就是这样的约定之一。

        尚未有人提及的是,一些外部工具可能依赖此类约定。

        例如,我经常使用流行的grep 替代品,它只允许在给定类型的文件中进行搜索。该程序会将.tpp 文件识别为C++,但不能识别.zz 文件

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-09-09
          • 2021-03-07
          • 1970-01-01
          • 2019-05-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多