引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
当前回答
我建议看看这个gcc页面,它讨论了模板实例化的“cfront”和“borland”模型之间的权衡。
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html
“borland”模型符合作者的建议,提供完整的模板定义,并多次编译。
它包含关于使用手动和自动模板实例化的明确建议。例如,“-repo”选项可用于收集需要实例化的模板。或者另一个选项是使用“-fno隐式模板”禁用自动模板实例化,以强制手动模板实例化。
根据我的经验,我依赖于为每个编译单元实例化的C++标准库和Boost模板(使用模板库)。对于我的大型模板类,我为所需的类型进行了一次手动模板实例化。
这是我的方法,因为我提供的是一个工作程序,而不是用于其他程序的模板库。这本书的作者Josuttis在模板库方面做了大量工作。
如果我真的担心速度,我想我会探索使用预编译头https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
这在许多编译器中得到了支持。然而,我认为使用模板头文件时,预编译头文件会很困难。
其他回答
这是完全正确的,因为编译器必须知道它的分配类型。模板类、函数、枚举等。。如果要将头文件公开或作为库的一部分(静态或动态),则也必须在头文件中实现,因为头文件不像c/cpp文件那样编译。如果编译器不知道该类型,则无法编译它。在.Net中,它可以编译,因为所有对象都派生自Object类。这不是.Net。
在头文件中编写声明和定义是一个好主意的另一个原因是为了可读性。假设Utility.h中有这样一个模板函数:
template <class T>
T min(T const& one, T const& theOther);
在Utility.cpp中:
#include "Utility.h"
template <class T>
T min(T const& one, T const& other)
{
return one < other ? one : other;
}
这需要这里的每个T类实现小于运算符(<)。当您比较两个尚未实现“<”的类实例时,它将抛出编译器错误。
因此,如果将模板声明和定义分开,则无法仅读取头文件来查看此模板的输入和输出,以便在自己的类上使用此API,尽管在这种情况下编译器会告诉您需要重写哪个运算符。
单独实现的方法如下。
内部_内部.h
template <typename T>
struct Foo
{
void doSomething(T param);
};
foo.tpp文件
#include "inner_foo.h"
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
小时
#include <foo.tpp>
主.cpp
#include <foo.h>
inner_fo.h具有前向声明。foo.tpp具有实现并包括inner_fo.h;而foo.h只有一行,包括foo.tpp。
编译时,将foo.h的内容复制到foo.tpp,然后将整个文件复制到foo.h,然后进行编译。这样,没有任何限制,并且命名是一致的,以换取一个额外的文件。
我这样做是因为代码的静态分析器在*.tpp中看不到类的前向声明时会中断。这在任何IDE中编写代码或使用YouCompleteMe或其他工具时都很烦人。
这是因为需要单独编译,并且模板是实例化样式多态性。
让我们更接近具体的解释。假设我有以下文件:
小时声明类MyClass<T>的接口foo.cpp定义类MyClass<T>的实现棒.cpp使用MyClass<int>
独立编译意味着我应该能够独立于bar.cpp编译foo.cpp。编译器完全独立地在每个编译单元上完成分析、优化和代码生成的所有艰苦工作;我们不需要做整个程序分析。只有链接器需要一次处理整个程序,而且链接器的工作要简单得多。
当我编译foo.cpp时,bar.cpp甚至不需要存在,但我应该仍然能够将我已经拥有的foo.o与我刚刚制作的bar.o链接在一起,而不需要重新编译foo.cpp。foo.cpp甚至可以被编译成一个动态库,在没有foo.cpp的情况下分发到其他地方,并与他们在我编写foo.cpp多年后编写的代码链接。
“实例化样式多态性”意味着模板MyClass<T>实际上不是一个通用类,它可以编译为可用于任何T值的代码。这会增加诸如装箱之类的开销,需要将函数指针传递给分配器和构造函数等。C++模板的目的是避免编写几乎相同的类MyClass_int、类MyClass_float等,但最终仍然能够编写出编译后的代码,就像我们分别编写了每个版本一样。因此,模板实际上就是模板;类模板不是类,它是为我们遇到的每个T创建新类的方法。模板不能编译成代码,只能编译实例化模板的结果。
因此,当编译foo.cpp时,编译器无法看到bar.cpp以知道需要MyClass<int>。它可以看到模板MyClass<T>,但不能为此发出代码(它是一个模板,而不是类)。编译bar.cpp时,编译器可以看到它需要创建MyClass<int>,但它看不到模板MyClass<t>(只有foo.h中的接口),因此无法创建它。
如果foo.cpp本身使用MyClass<int>,那么编译foo.cpp时将生成该代码,因此当bar.o链接到foo.o时,它们可以连接起来并工作。我们可以使用这个事实,通过编写一个模板,在.cpp文件中实现一组有限的模板实例化。但bar.cpp无法将模板用作模板,并在它喜欢的任何类型上实例化它;它只能使用foo.cpp作者认为可以提供的模板类的预先存在的版本。
您可能会认为,在编译模板时,编译器应该“生成所有版本”,在链接过程中过滤掉从未使用过的版本。除了由于指针和数组等“类型修改器”特性,即使是内置类型也可以产生无限数量的类型,因此这种方法将面临巨大的开销和极端困难外,现在我通过添加以下内容来扩展程序时会发生什么:
巴兹卡普声明并实现类BazPrivate,并使用MyClass<BazPrivate>
除非我们
每次我们更改程序中的任何其他文件时,都必须重新编译foo.cpp,以防它添加了MyClass的新实例<T>要求baz.cpp包含(可能通过headerincludes)MyClass<T>的完整模板,以便编译器可以在编译baz.cpp期间生成MyClass<BazPrivate>。
没有人喜欢(1),因为整个程序分析编译系统需要很长时间才能编译,而且如果没有源代码,就无法分发编译后的库。所以我们有(2)。
我建议看看这个gcc页面,它讨论了模板实例化的“cfront”和“borland”模型之间的权衡。
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html
“borland”模型符合作者的建议,提供完整的模板定义,并多次编译。
它包含关于使用手动和自动模板实例化的明确建议。例如,“-repo”选项可用于收集需要实例化的模板。或者另一个选项是使用“-fno隐式模板”禁用自动模板实例化,以强制手动模板实例化。
根据我的经验,我依赖于为每个编译单元实例化的C++标准库和Boost模板(使用模板库)。对于我的大型模板类,我为所需的类型进行了一次手动模板实例化。
这是我的方法,因为我提供的是一个工作程序,而不是用于其他程序的模板库。这本书的作者Josuttis在模板库方面做了大量工作。
如果我真的担心速度,我想我会探索使用预编译头https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
这在许多编译器中得到了支持。然而,我认为使用模板头文件时,预编译头文件会很困难。