Mai*_*nID 1660 c++ templates c++-faq
引自C++标准库:教程和手册:
目前使用模板的唯一可移植方法是使用内联函数在头文件中实现它们.
为什么是这样?
(澄清:头文件不是唯一的便携式解决方案.但它们是最方便的便携式解决方案.)
Luc*_*lle 1453
它是不是需要把执行的头文件,看到这个答案的末尾替代解决方案.
无论如何,代码失败的原因是,在实例化模板时,编译器会创建一个具有给定模板参数的新类.例如:
template<typename T>
struct Foo
{
T bar;
void doSomething(T param) {/* do stuff using T */}
};
// somewhere in a .cpp
Foo<int> f;
Run Code Online (Sandbox Code Playgroud)
读取此行时,编译器将创建一个新类(让我们调用它FooInt
),这相当于以下内容:
struct FooInt
{
int bar;
void doSomething(int param) {/* do stuff using int */}
}
Run Code Online (Sandbox Code Playgroud)
因此,编译器需要访问方法的实现,以使用模板参数(在本例中int
)实例化它们.如果这些实现不在标头中,则它们将无法访问,因此编译器将无法实例化模板.
一个常见的解决方案是在头文件中编写模板声明,然后在实现文件(例如.tpp)中实现该类,并在头的末尾包含此实现文件.
// Foo.h
template <typename T>
struct Foo
{
void doSomething(T param);
};
#include "Foo.tpp"
// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
Run Code Online (Sandbox Code Playgroud)
这样,实现仍然与声明分离,但编译器可以访问.
另一种解决方案是保持实现分离,并显式实例化您需要的所有模板实例:
// Foo.h
// no implementation
template <typename T> struct Foo { ... };
//----------------------------------------
// Foo.cpp
// implementation of Foo's methods
// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float
Run Code Online (Sandbox Code Playgroud)
如果我的解释不够清楚,你可以看一下关于这个主题的C++ Super-FAQ.
MaH*_*uJa 239
这里有很多正确答案,但我想补充一下(为了完整性):
如果您在实现cpp文件的底部对模板将使用的所有类型进行显式实例化,则链接器将能够像往常一样找到它们.
编辑:添加显式模板实例化的示例.在定义模板后使用,并且已定义所有成员函数.
template class vector<int>;
Run Code Online (Sandbox Code Playgroud)
这将实例化(并因此使链接器可用)类及其所有成员函数(仅).类似的语法适用于模板函数,因此如果您有非成员运算符重载,则可能需要对它们执行相同的操作.
上面的例子是相当无用的,因为vector是在头文件中完全定义的,除非公共包含文件(预编译头文件?)使用extern template class vector<int>
它以防止它在使用vector的所有其他(1000?)文件中实例化它.
Ben*_*Ben 232
这是因为需要单独编译,因为模板是实例化式多态.
让我们更接近具体解释.说我有以下文件:
class MyClass<T>
class MyClass<T>
MyClass<int>
独立编译意味着我应该能够编写Foo.cpp中独立地bar.cpp.编译器完全独立地在每个编译单元上完成分析,优化和代码生成的所有艰苦工作; 我们不需要进行全程序分析.只有链接器需要立即处理整个程序,并且链接器的工作要容易得多.
当我编译foo.cpp时,bar.cpp甚至不需要存在,但是我仍然可以链接foo.o我已经和bar一起了.我刚刚制作了,而不需要重新编译foo .cpp.foo.cpp甚至可以被编译成一个动态库,在没有foo.cpp的情况下分布在其他地方,并且在我编写foo.cpp之后的几年内与它们编写的代码相关联.
"实例化样式多态"意味着模板MyClass<T>
实际上不是一个通用类,可以编译为可以适用于任何值的代码T
.这会增加开销,如拳击,需要对函数指针传递给分配器和构造等的C++模板的目的是为了避免写几乎相同class MyClass_int
,class 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的作者认为提供的模板化类的预先存在的版本.
您可能认为编译模板时编译器应"生成所有版本",并且在链接期间过滤掉从未使用过的版本.除了巨大的开销和这种方法所面临的极端困难之外,因为"类型修饰符"功能(如指针和数组)甚至只允许内置类型产生无数种类型,当我现在扩展程序时会发生什么通过增加:
class BazPrivate
和使用MyClass<BazPrivate>
除非我们这样做,否则没有办法可行
MyClass<T>
MyClass<T>
,以便编译器可以MyClass<BazPrivate>
在编译baz.cpp期间生成.没有人喜欢(1),因为整个程序分析编译系统需要永远编译,因为它使得在没有源代码的情况下分发编译库成为不可能.所以我们改为(2).
Dav*_*nak 75
在将模板实际编译为目标代码之前,模板需要由编译器实例化.只有在模板参数已知的情况下才能实现此实例化.现在想象一下模板函数在其中声明a.h
,定义a.cpp
和使用的场景b.cpp
.在a.cpp
编译时,不一定知道即将进行的编译b.cpp
将需要模板的实例,更不用说具体的实例.对于更多的头文件和源文件,情况可能会变得更加复杂.
有人可以说,编译器可以变得更聪明,可以"展望"模板的所有用途,但我确信创建递归或其他复杂场景并不困难.AFAIK,编译器不会这样做.正如Anton所指出的,一些编译器支持模板实例化的显式导出声明,但并非所有编译器都支持它(但是?).
Dev*_*lar 58
实际上,之前C++ 11标准中定义的export
关键字将使其能够在头文件中声明的模板和其他地区实施.
没有一个流行的编译器实现了这个关键字.我所知道的唯一一个是Edison Design Group编写的前端,它由Comeau C++编译器使用.所有其他人都要求您在头文件中编写模板,因为编译器需要模板定义才能进行正确的实例化(正如其他人已经指出的那样).
因此,ISO C++标准委员会决定export
使用C++ 11 删除模板的功能.
Ant*_*lev 35
虽然标准C++没有这样的要求,但是一些编译器要求所有函数和类模板都需要在它们使用的每个转换单元中可用.实际上,对于那些编译器,模板函数的主体必须在头文件中可用.重复:这意味着那些编译器不允许在非头文件中定义它们,例如.cpp文件
有一个导出关键字可以缓解这个问题,但它远不是可移植的.
Ger*_*ago 28
必须在头文件中使用模板,因为编译器需要实例化不同版本的代码,具体取决于为模板参数提供/推导的参数.请记住,模板不直接代表代码,而是代表该代码的多个版本的模板.在.cpp
文件中编译非模板函数时,您正在编译具体的函数/类.对于可以使用不同类型实例化的模板,情况并非如此,即,在用具体类型替换模板参数时必须发出具体代码.
export
关键字有一个功能,用于单独编译.该export
功能已弃用C++11
,AFAIK中只有一个编译器实现了该功能.你不应该使用export
.单独的编译是不可能的C++
或者C++11
但也许C++17
,如果概念使其在,我们可以有单独的编译的一些方法.
要实现单独的编译,必须单独进行模板体检查.似乎可以通过概念实现解决方案.看看最近在标准委员会会议上提交的这篇论文.我认为这不是唯一的要求,因为您仍然需要在用户代码中实例化模板代码的代码.
模板的单独编译问题我猜这也是迁移到模块时出现的问题,目前正在进行中.
Ben*_*oît 15
这意味着定义模板类的方法实现的最便携方式是在模板类定义中定义它们.
template < typename ... >
class MyClass
{
int myMethod()
{
// Not just declaration. Add method implementation here
}
};
Run Code Online (Sandbox Code Playgroud)
laf*_*blu 13
即使上面有很多好的解释,我也错过了将模板分成标题和正文的实用方法.
我主要担心的是当我更改其定义时,避免重新编译所有模板用户.
模板体中的所有模板实例化对我来说都不是一个可行的解决方案,因为模板作者可能不知道它的用法和模板用户是否有权修改它.
我采用了以下方法,该方法也适用于较旧的编译器(gcc 4.3.4,aCC A.03.13).
对于每个模板使用,在其自己的头文件中有一个typedef(从UML模型生成).它的主体包含实例化(最终在最后链接的库中).
模板的每个用户都包含该头文件并使用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
Run Code Online (Sandbox Code Playgroud)
MyTemplate.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;
}
Run Code Online (Sandbox Code Playgroud)
MyInstantiatedTemplate.h:
#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"
typedef MyTemplate< int > MyInstantiatedTemplate;
#endif
Run Code Online (Sandbox Code Playgroud)
MyInstantiatedTemplate.cpp:
#include "MyTemplate.cpp"
template class MyTemplate< int >;
Run Code Online (Sandbox Code Playgroud)
main.cpp中:
#include "MyInstantiatedTemplate.h"
int main()
{
MyInstantiatedTemplate m(100);
m.dump();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这样,只需要重新编译模板实例化,而不是所有模板用户(和依赖项).
当您在编译步骤中使用模板时,编译器将为每个模板实例化生成代码。在编译和链接过程中,.cpp 文件被转换为纯对象或机器代码,其中包含引用或未定义的符号,因为包含在 main.cpp 中的 .h 文件还没有实现。这些已准备好与另一个定义模板实现的目标文件链接,因此您有一个完整的 a.out 可执行文件。
但是,由于模板需要在编译步骤中进行处理,以便为您定义的每个模板实例生成代码,因此简单地编译与其头文件分开的模板是行不通的,因为它们总是齐头并进,这是出于这个原因每个模板实例化都是一个全新的类。在常规类中,您可以将 .h 和 .cpp 分开,因为 .h 是该类的蓝图,而 .cpp 是原始实现,因此可以定期编译和链接任何实现文件,但是使用模板 .h 是如何使用的蓝图类应该看起来不是对象应该是什么样子,这意味着模板 .cpp 文件不是类的原始常规实现,它只是类的蓝图,因此 .h 模板文件的任何实现都可以'
因此模板永远不会单独编译,并且只会在您在其他源文件中有具体实例的地方编译。但是具体的实例化需要知道模板文件的实现,因为简单的修改typename T
在 .h 文件中使用具体类型不会完成这项工作,因为 .cpp 是要链接的,稍后我找不到它,因为记住模板是抽象的并且无法编译,所以我被迫现在就给出实现,所以我知道要编译和链接什么,现在我有了实现,它被链接到封闭的源文件中。基本上,当我实例化一个模板时,我需要创建一个全新的类,如果我不知道该类在使用我提供的类型时应该是什么样子,我就不能这样做,除非我通知编译器模板实现,所以现在编译器可以T
用我的类型替换并创建一个可以编译和链接的具体类。
总而言之,模板是类的外观蓝图,类是对象外观的蓝图。我无法将模板与其具体实例分开编译,因为编译器只编译具体类型,换句话说,模板至少在 C++ 中是纯语言抽象。可以这么说,我们必须去抽象模板,我们通过给它们一个具体的类型来处理,这样我们的模板抽象就可以转换成一个常规的类文件,反过来,它可以正常编译。将模板 .h 文件和模板 .cpp 文件分开是没有意义的。这是荒谬的,因为 .cpp 和 .h 的分离只是 .cpp 可以单独编译和单独链接的地方,使用模板,因为我们无法单独编译它们,因为模板是一种抽象,
意思typename T
是在编译步骤而不是链接步骤中T
被替换,所以如果我尝试编译模板而不被替换为对编译器完全没有意义的具体值类型,因此无法创建对象代码,因为它没有知道是什么T
。
在技术上可以创建某种功能来保存 template.cpp 文件并在它在其他来源中找到它们时切换类型,我认为该标准确实有一个关键字export
可以让您将模板放在单独的cpp 文件,但实际上没有多少编译器实现了这一点。
顺便提一下,在对模板类进行特化时,您可以将标题与实现分开,因为定义特化意味着我专门针对可以单独编译和链接的具体类型。
只是在这里添加一些值得注意的东西。当模板化类的方法不是函数模板时,可以在实现文件中很好地定义它们。
我的队列.hpp:
template <class T>
class QueueA {
int size;
...
public:
template <class T> T dequeue() {
// implementation here
}
bool isEmpty();
...
}
Run Code Online (Sandbox Code Playgroud)
我的队列.cpp:
// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
return this->size == 0;
}
main()
{
QueueA<char> Q;
...
}
Run Code Online (Sandbox Code Playgroud)
小智 7
如果关注的是通过编译.h作为所有使用它的.cpp模块的一部分而产生的额外编译时间和二进制大小膨胀,在许多情况下,你可以做的是使模板类从非模板化的基类下降接口的非类型相关部分,该基类可以在.cpp文件中实现它.
小智 6
这是完全正确的,因为编译器必须知道它的分配类型.所以模板类,函数,枚举等也必须在头文件中实现,如果它要公开或者是库的一部分(静态或动态),因为头文件的编译不像c/cpp文件那样是.如果编译器不知道类型是无法编译它.在.Net中它可以因为所有对象都派生自Object类.这不是.Net.
一种单独实现的方法如下。
//inner_foo.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
}
//foo.h
#include <foo.tpp>
//main.cpp
#include <foo.h>
Run Code Online (Sandbox Code Playgroud)
inner_foo 有前向声明。foo.tpp 有实现并包含inner_foo.h;和 foo.h 将只有一行,包括 foo.tpp。
在编译时,foo.h 的内容被复制到 foo.tpp,然后整个文件被复制到 foo.h,然后编译。这样,没有限制,命名一致,换来一个额外的文件。
我这样做是因为代码的静态分析器在 *.tpp 中没有看到类的前向声明时会中断。在任何 IDE 中编写代码或使用 YouCompleteMe 或其他工具时,这很烦人。
归档时间: |
|
查看次数: |
459466 次 |
最近记录: |