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