将模板化的C ++类拆分成.hpp / .cpp文件 – 可以吗?

我遇到了错误,试图编译一个.hpp.cpp文件之间拆分的C ++模板类:

 $ g++ -c -o main.o main.cpp $ g++ -c -o stack.o stack.cpp $ g++ -o main main.o stack.o main.o: In function `main': main.cpp:(.text+0xe): undefined reference to 'stack<int>::stack()' main.cpp:(.text+0x1c): undefined reference to 'stack<int>::~stack()' collect2: ld returned 1 exit status make: *** [program] Error 1 

这是我的代码:

stack.hpp

 #ifndef _STACK_HPP #define _STACK_HPP template <typename Type> class stack { public: stack(); ~stack(); }; #endif 

stack.cpp

 #include <iostream> #include "stack.hpp" template <typename Type> stack<Type>::stack() { std::cerr << "Hello, stack " << this << "!" << std::endl; } template <typename Type> stack<Type>::~stack() { std::cerr << "Goodbye, stack " << this << "." << std::endl; } 

main.cpp

 #include "stack.hpp" int main() { stack<int> s; return 0; } 

ld当然是正确的:符号不在stack.o

这个问题的答案没有帮助,因为我已经这么说了。
这可能会有所帮助,但是我不想将每一个方法都移到.hpp文件中 – 我不应该这样做吗?

是将.cpp文件中的所有内容都移动到.hpp文件的唯一合理解决scheme,并且只包含所有内容,而不是作为独立的对象文件链接。 这似乎太难看了! 在这种情况下,我还可以恢复到以前的状态,并将stack.cpp重命名为stack.hpp并完成它。

不可能在独立的cpp文件中编写模板类的实现并编译。 如果有人声称,所有这些方法都是模拟单独cpp文件的使用方法,但实际上,如果您打算编写模板类库并将其与头文件和lib文件一起分发以隐藏实现,那么这是不可能的。

要知道为什么,让我们看看编译过程。 头文件永远不会被编译。 他们只是预处理。 预处理的代码然后用实际编译的cpp文件杵着。 现在,如果编译器必须为对象生成适当的内存布局,则需要知道模板类的数据types。

实际上,必须了解的是,模板类根本不是一个类,而是一个类的模板,其声明和定义由编译器在从参数中获取数据types的信息后在编译时生成。 只要内存布局不能创build,方法定义的指令就不能生成。 记住类方法的第一个参数是“this”运算符。 所有的类方法都被转换成单个方法,其中名称是混合的,第一个参数是它所操作的对象。 “this”参数实际上是告诉编译器不能使用模板类的对象的大小,除非用户用有效的types参数实例化对象。 在这种情况下,如果将方法定义放在单独的cpp文件中并尝试编译,则不会使用类信息生成对象文件本身。 编译不会失败,它会生成目标文件,但不会为目标文件中的模板类生成任何代码。 这就是为什么链接器无法find目标文件中的符号,并且构build失败的原因。

现在有什么替代隐藏重要的实施细节? 众所周知,将接口与实现分离的主要目标是以二进制forms隐藏实现细节。 这是你必须分离数据结构和algorithm的地方。 您的模板类必须仅表示数据结构而不是algorithm。 这使您能够在单独的非模板化类库中隐藏更多有价值的实现细节,其中的类可以在模板类上工作,或者只是用它们来保存数据。 模板类实际上包含更less的代码来分配,获取和设置数据。 其余的工作将由algorithm类来完成。

我希望这个讨论会有所帮助。

可能的,只要你知道你将需要什么样的实例化。

将下面的代码添加到stack.cpp的末尾,它将起作用:

 template class stack<int>; 

所有的非模板堆栈方法将被实例化,并且链接步骤将正常工作。

你可以这样做

 // xyz.h #ifndef _XYZ_ #define _XYZ_ template <typename XYZTYPE> class XYZ { //Class members declaration }; #include "xyz.cpp" #endif //xyz.cpp #ifdef _XYZ_ //Class definition goes here #endif 

Daniweb已经讨论过这个问题

也在FAQ中使用C ++导出关键字。

不,这是不可能的。 不是没有export关键字,对于所有的意图和目的并不存在。

最好的办法是将你的函数实现放在一个“.tcc”或“.tpp”文件中,#包括.hpp文件末尾的.tcc文件。 然而,这只不过是一个美丽的东西。 它仍然像在头文件中实现一切一样。 这只是您使用模板所付出的代价。

我相信有两个主要的原因,试图将模板代码分为头和cpp:

一个是纯粹的优雅。 我们都喜欢编写易于阅读,pipe理和稍后可重用的代码。

其他的是减less编译时间。

我目前(一如既往)将编程仿真软件与OpenCL结合在一起,我们希望保留代码,以便根据硬件能力使用float(cl_float)或double(cl_double)types运行。 现在,这是在代码的开头使用#define REAL完成的,但这不是很优雅。 更改所需的精度需要重新编译应用程序。 由于没有真正的运行时间types,我们必须暂时与此相适应。 幸运的是,OpenCL内核是编译运行时的,简单的sizeof(REAL)允许我们相应地改变内核代码的运行时间。

更大的问题是即使应用程序是模块化的,开发辅助类(例如那些预先计算模拟常量的类)也必须被模板化。 这些类在类依赖树的顶部至less出现一次,因为最终的模板类Simulation将具有这些工厂类之一的实例,这意味着几乎每次我对工厂类进行小的改变时,整个软件必须重build。 这是非常烦人的,但我似乎无法find更好的解决scheme。

有时,如果你可以将所有的模板参数都提取到非模板类(可能是types不安全的)中,那么有可能隐藏在cpp文件中的大部分实现。 那么标题将包含对该类的redirect调用。 与“模板膨胀”问题战斗时使用类似的方法。

如果你知道你的堆栈将使用什么types,你可以在cpp文件中实例化它们,并保留所有相关的代码。

也可以将这些文件导出到DLL(!)中,但是要正确得到语法(MS特定的__declspec(dllexport)和export关键字组合)非常困难。

我们已经在一个模拟double / float的math/几何库中使用过,但是有相当多的代码。 (我当时search了这个,不过今天没有这个代码。)

问题是模板不会生成实际的类,它只是一个告诉编译器如何生成类的模板 。 你需要生成一个具体的类。

简单而自然的方法是将方法放在头文件中。 但还有另一种方法。

在您的.cpp文件中,如果您有需要的每个模板实例化和方法的引用,那么编译器将在那里生成它们以供整个项目使用。

新的stack.cpp:

 #include <iostream> #include "stack.hpp" template <typename Type> stack<Type>::stack() { std::cerr << "Hello, stack " << this << "!" << std::endl; } template <typename Type> stack<Type>::~stack() { std::cerr << "Goodbye, stack " << this << "." << std::endl; } static void DummyFunc() { static stack<int> stack_int; // generates the constructor and destructor code // ... any other method invocations need to go here to produce the method code } 

只有当你在#include "stack.cpp处包含#include "stack.cpp ”时,我才会推荐这种方法,如果这个实现比较大,而且如果你把.cpp文件重命名为另一个扩展名,以区别于普通代码。

这是一个相当古老的问题,但是我觉得在cppcon 2016上观看Arthur O'Dwyer的介绍很有意思。 好的解释,很多主题都涵盖了,一定要看。

由于模板是在需要时编译的,因此这会强制对多文件项目进行限制:模板类或函数的实现(定义)必须与其声明位于同一个文件中。 这意味着我们不能在单独的头文件中分离接口,并且我们必须在使用模板的任何文件中包含接口和实现。

您需要拥有hpp文件中的所有内容。 问题是这些类只有在编译器发现它们被某个OTHER cpp文件需要时才真正创build – 所以它必须有足够的代码来编译模板类。

我倾向于做的一件事是尝试将我的模板分割成通用的非模板化部分(可以在cpp / hpp之间分割)和inheritance非模板化类的特定于types的模板部分。

另一种可能性是做类似的事情:

 #ifndef _STACK_HPP #define _STACK_HPP template <typename Type> class stack { public: stack(); ~stack(); }; #include "stack.cpp" // Note the include. The inclusion // of stack.h in stack.cpp must be // removed to avoid a circular include. #endif 

我不喜欢这个build议作为风格的问题,但它可能适合你。

“导出”关键字是从模板声明中分离出模板实现的方式。 这是在没有现有实现的情况下在C ++标准中引入的。 在适当的时候,只有几个编译器实际上实现了它。 请阅读关于导出的Inform IT文章中的深入信息

1)记住分离.h和.cpp文件的主要原因是将类实现隐藏为一个单独编译的Obj代码,该代码可以链接到包含该类的.h的用户代码。

2)非模板类具有在.h和.cpp文件中具体定义的所有variables。 因此,在编译/翻译之前,编译器将拥有关于类中使用的所有数据types的信息。生成对象/机器代码在类的用户实例化通过所需数据的对象之前,模板类没有关于特定数据types的信息types:

  TClass<int> myObj; 

3)只有在这个实例化之后,编译器才生成模板类的特定版本以匹配传递的数据types。

4)因此,.cpp不能单独编译而不知道用户的具体数据types。 所以它必须作为源代码保留在“.h”中,直到用户指定所需的数据types,然后才能生成一个特定的数据types,然后编译

我正在使用Visual Studio 2010,如果您想要将文件分割为.h和.cpp,请在.h文件末尾包含您的cpp头