在我的一个 C++ 项目中,我使用了许多模板。由于我不能将它们放在 *.cpp 文件中,因此它们的整个功能目前都存在于标题中。
但这一方面会弄乱头文件,另一方面会导致编译时间过长。如何以干净的方式处理模板化函数的实现?
您可以创建一个新的头文件library_detail.hpp
并将其包含在原始头文件中。
我见过一些人也使用.t
或.template
扩展来命名这个实现头文件。
实际上并没有要求模板必须在标题中。他们很可能在翻译单元中。唯一的要求是编译器要么能够在使用它们时隐式实例化它们,要么能够显式实例化它们。
基于此将模板化代码分为标头和非标头是否可行在很大程度上取决于正在做什么。它工作得相当好,例如,对于 IOStreams 库,因为它在实践中只为字符类型char
和wchar_t
. 编写显式实例化相当简单,即使有更多的字符类型,例如char16_t
和char32_t
,它仍然是可行的。另一方面,std::vector<T>
以类似的方式分离模板是相当不可行的。
在子系统之间的接口中使用通用模板std::vector<T>
很快就开始成为一个主要问题:虽然具体实例化或选定实例化是可以的,因为子系统可以在没有模板的情况下实现,使用任意实例化将迫使整个系统成为所有模板。这样做在任何实际应用程序中都是不可行的,这些应用程序通常是几百万行代码。
这相当于使用完全类型化的编译防火墙,并且不在子系统之间使用任意模板。为了简化子系统接口的使用,可能存在瘦模板包装器,例如,将一种容器类型转换为另一种容器,或者在可行的情况下对模板参数进行类型擦除。然而,需要认识到,编译分离通常是以运行时性能为代价的:调用virtual
函数比调用inline
函数要昂贵得多。因此,子系统之间的抽象可能与子系统内的抽象非常不同。例如,迭代器是很好的内部抽象。在子系统之间,特定的容器,例如,std::vector<X>
对于某种类型的 a X
,往往更有效。
请注意,模板的构建时交互是它们对特定实例化的依赖所固有的。也就是说,即使声明和模板定义有一个相当不同的系统,例如,以模块系统的形式而不是使用头文件,将所有东西都做成模板是不可行的。使用模板实现本地灵活性效果很好,但如果不修复大型项目中的全局实例化,它们就无法工作。
最后是一个插件:这是一篇关于如何组织实现模板的源代码的文章。
可以帮助的一件事是将不依赖于模板参数的功能分解为可以在实现文件中定义的非模板化帮助函数。
例如,如果您正在实现自己的向量类,您可以将大部分内存管理分解为一个非模板类,该类仅适用于无类型的字节数组,并在 .cpp 文件中定义其成员函数。实际需要知道元素类型的函数在标头中的模板化类中实现,并将大部分工作委托给非模板化助手。