引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
当前回答
单独实现的方法如下。
内部_内部.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 4.3.4,aCC A.03.13)。
对于每个模板的使用,在其自己的头文件(从UML模型生成)中都有一个typedef。它的主体包含实例化(最终在一个库中,该库在末尾链接)。
模板的每个用户都包含该头文件并使用typedef。
示意图示例:
MyTemplate.h:
#ifndef MyTemplate_h
#define MyTemplate_h 1
template <class T>
class MyTemplate
{
public:
MyTemplate(const T& rt);
void dump();
T t;
};
#endif
我的模板.cpp:
#include "MyTemplate.h"
#include <iostream>
template <class T>
MyTemplate<T>::MyTemplate(const T& rt)
: t(rt)
{
}
template <class T>
void MyTemplate<T>::dump()
{
cerr << t << endl;
}
MyInstantiatedTemplate.h:
#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"
typedef MyTemplate< int > MyInstantiatedTemplate;
#endif
我的实例化模板.cpp:
#include "MyTemplate.cpp"
template class MyTemplate< int >;
main.cpp:
#include "MyInstantiatedTemplate.h"
int main()
{
MyInstantiatedTemplate m(100);
m.dump();
return 0;
}
这样,只需要重新编译模板实例,而不是所有模板用户(和依赖项)。
这里有很多正确的答案,但我想补充一下(为了完整):
如果在实现cpp文件的底部,对模板将使用的所有类型进行显式实例化,链接器将能够像往常一样找到它们。
编辑:添加显式模板实例化示例。在定义模板并定义所有成员函数后使用。
template class vector<int>;
这将实例化(从而使链接器可用)类及其所有成员函数(仅限)。类似的语法适用于函数模板,因此如果您有非成员运算符重载,则可能需要对这些重载执行相同的操作。
上面的例子是非常无用的,因为vector是在头文件中完全定义的,除非公共include文件(预编译头文件?)使用外部模板类vector<int>,以防止它在所有其他使用vector的(1000?)文件中实例化。
这是完全正确的,因为编译器必须知道它的分配类型。模板类、函数、枚举等。。如果要将头文件公开或作为库的一部分(静态或动态),则也必须在头文件中实现,因为头文件不像c/cpp文件那样编译。如果编译器不知道该类型,则无法编译它。在.Net中,它可以编译,因为所有对象都派生自Object类。这不是.Net。
我建议看看这个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
这在许多编译器中得到了支持。然而,我认为使用模板头文件时,预编译头文件会很困难。