将模板化的 C++ 类拆分为 .hpp/.cpp 文件——这可能吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1724036/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Splitting templated C++ classes into .hpp/.cpp files--is it possible?
提问by exscape
I am getting errors trying to compile a C++ template class which is split between a .hpp
and .cpp
file:
尝试编译在 a.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
Here is my code:
这是我的代码:
stack.hpp:
堆栈.hpp:
#ifndef _STACK_HPP
#define _STACK_HPP
template <typename Type>
class stack {
public:
stack();
~stack();
};
#endif
stack.cpp:
堆栈.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:
主.cpp:
#include "stack.hpp"
int main() {
stack<int> s;
return 0;
}
ld
is of course correct: the symbols aren't in stack.o
.
ld
当然是正确的:符号不在stack.o
.
The answer to this questiondoes not help, as I'm already doing as it says.
This onemight help, but I don't want to move every single method into the .hpp
file—I shouldn't have to, should I?
这个问题的答案没有帮助,因为我已经按照它说的做了。
这个可能会有所帮助,但我不想将每个方法都移到.hpp
文件中——我不应该这样做,是吗?
Is the only reasonable solution to move everything in the .cpp
file to the .hpp
file, and simply include everything, rather than link in as a standalone object file? That seems awfullyugly! In that case, I might as well revert to my previous state and rename stack.cpp
to stack.hpp
and be done with it.
将.cpp
文件中的所有内容移动到.hpp
文件中,并简单地包含所有内容,而不是作为独立的目标文件链接,这是唯一合理的解决方案吗?这看起来非常丑陋!在这种情况下,我还不如恢复到我以前的状态,并重新命名stack.cpp
,以stack.hpp
和与它做。
回答by Sharjith N.
It is not possible to write the implementation of a template class in a separate cpp file and compile. All the ways to do so, if anyone claims, are workarounds to mimic the usage of separate cpp file but practically if you intend to write a template class library and distribute it with header and lib files to hide the implementation, it is simply not possible.
无法在单独的 cpp 文件中编写模板类的实现并进行编译。如果有人声称,所有这样做的方法都是模仿单独 cpp 文件使用的变通方法,但实际上,如果您打算编写模板类库并将其与头文件和 lib 文件一起分发以隐藏实现,这是不可能的.
To know why, let us look at the compilation process. The header files are never compiled. They are only preprocessed. The preprocessed code is then clubbed with the cpp file which is actually compiled. Now if the compiler has to generate the appropriate memory layout for the object it needs to know the data type of the template class.
要知道为什么,让我们看看编译过程。头文件永远不会被编译。它们只是经过预处理。然后将预处理的代码与实际编译的 cpp 文件结合在一起。现在,如果编译器必须为对象生成适当的内存布局,则它需要知道模板类的数据类型。
Actually it must be understood that template class is not a class at all but a template for a class the declaration and definition of which is generated by the compiler at compile time after getting the information of the data type from the argument. As long as the memory layout cannot be created, the instructions for the method definition cannot be generated. Remember the first argument of the class method is the 'this' operator. All class methods are converted into individual methods with name mangling and the first parameter as the object which it operates on. The 'this' argument is which actually tells about size of the object which incase of template class is unavailable for the compiler unless the user instantiates the object with a valid type argument. In this case if you put the method definitions in a separate cpp file and try to compile it the object file itself will not be generated with the class information. The compilation will not fail, it would generate the object file but it won't generate any code for the template class in the object file. This is the reason why the linker is unable to find the symbols in the object files and the build fails.
实际上必须理解,模板类根本不是一个类,而是一个类的模板,该类的声明和定义是编译器在编译时从参数中获取数据类型信息后生成的。只要无法创建内存布局,就无法生成方法定义的指令。记住类方法的第一个参数是“this”操作符。所有类方法都被转换为具有名称修饰和第一个参数作为其操作对象的单独方法。'this' 参数实际上说明了对象的大小,除非用户使用有效的类型参数实例化对象,否则编译器无法使用模板类。在这种情况下,如果您将方法定义放在单独的 cpp 文件中并尝试编译它,则不会生成带有类信息的目标文件本身。编译不会失败,它会生成目标文件,但不会为目标文件中的模板类生成任何代码。这就是链接器无法在目标文件中找到符号并且构建失败的原因。
Now what is the alternative to hide important implementation details? As we all know the main objective behind separating interface from implementation is hiding implementation details in binary form. This is where you must separate the data structures and algorithms. Your template classes must represent only data structures not the algorithms. This enables you to hide more valuable implementation details in separate non-templatized class libraries, the classes inside which would work on the template classes or just use them to hold data. The template class would actually contain less code to assign, get and set data. Rest of the work would be done by the algorithm classes.
现在隐藏重要实现细节的替代方法是什么?众所周知,将接口与实现分离的主要目的是以二进制形式隐藏实现细节。这是您必须分离数据结构和算法的地方。您的模板类必须仅表示数据结构而不是算法。这使您能够在单独的非模板化类库中隐藏更有价值的实现细节,其中的类可以在模板类上工作或仅使用它们来保存数据。模板类实际上包含更少的代码来分配、获取和设置数据。其余的工作将由算法类完成。
I hope this discussion would be helpful.
我希望这个讨论会有所帮助。
回答by Beno?t
It ispossible, as long as you know what instantiations you are going to need.
这是可能的,只要您知道您将需要哪些实例化。
Add the following code at the end of stack.cpp and it'll work :
在 stack.cpp 的末尾添加以下代码,它将起作用:
template class stack<int>;
All non-template methods of stack will be instantiated, and linking step will work fine.
堆栈的所有非模板方法都将被实例化,链接步骤将正常工作。
回答by Sadanand
You can do it in this way
你可以这样做
// 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
This has been discussed in Daniweb
这已在Daniweb 中讨论过
Also in FAQbut using C++ export keyword.
同样在FAQ 中,但使用 C++ 导出关键字。
回答by Charles Salvia
No, it's not possible. Not without the export
keyword, which for all intents and purposes doesn't really exist.
不,这不可能。不是没有export
关键字,就所有意图和目的而言,它实际上并不存在。
The best you can do is put your function implementations in a ".tcc" or ".tpp" file, and #include the .tcc file at the end of your .hpp file. However this is merely cosmetic; it's still the same as implementing everything in header files. This is simply the price you pay for using templates.
您能做的最好的事情是将您的函数实现放在“.tcc”或“.tpp”文件中,并在 .hpp 文件的末尾#include .tcc 文件。然而,这只是装饰性的;它仍然与在头文件中实现所有内容相同。这只是您为使用模板而付出的代价。
回答by Meteorhead
I believe there are two main reasons for trying to seperate templated code into a header and a cpp:
我认为尝试将模板化代码分离到 header 和 cpp 有两个主要原因:
One is for mere elegance. We all like to write code that is wasy to read, manage and is reusable later.
一种是纯粹的优雅。我们都喜欢编写易于阅读、管理和以后可重用的代码。
Other is reduction of compilation times.
另一个是减少编译时间。
I am currently (as always) coding simulation software in conjuction with OpenCL and we like to keep code so it can be run using float (cl_float) or double (cl_double) types as needed depending on HW capability. Right now this is done using a #define REAL at the beginning of the code, but this is not very elegant. Changing desired precision requires recompiling the application. Since there are no real run-time types, we have to live with this for the time being. Luckily OpenCL kernels are compiled runtime, and a simple sizeof(REAL) allows us to alter the kernel code runtime accordingly.
我目前(一如既往)与 OpenCL 结合编写仿真软件,我们喜欢保留代码,以便它可以根据硬件功能根据需要使用 float (cl_float) 或 double (cl_double) 类型运行。现在这是在代码的开头使用 #define REAL 来完成的,但这不是很优雅。更改所需的精度需要重新编译应用程序。由于没有真正的运行时类型,我们暂时必须接受它。幸运的是,OpenCL 内核是编译运行时,一个简单的 sizeof(REAL) 允许我们相应地更改内核代码运行时。
The much bigger problem is that even though the application is modular, when developing auxiliary classes (such as those that pre-calculate simulation constants) also have to be templated. These classes all appear at least once on the top of the class dependency tree, as the final template class Simulation will have an instance of one of these factory classes, meaning that practically every time I make a minor change to the factory class, the entire software has to be rebuilt. This is very annoying, but I cannot seem to find a better solution.
更大的问题是,即使应用程序是模块化的,在开发辅助类(例如那些预先计算模拟常数的类)时也必须模板化。这些类都至少在类依赖树的顶部出现一次,因为最终的模板类 Simulation 将拥有这些工厂类之一的实例,这意味着实际上每次我对工厂类进行细微更改时,整个必须重建软件。这很烦人,但我似乎找不到更好的解决方案。
回答by lyricat
Only if you #include "stack.cpp
at the end of stack.hpp
. I'd only recommend this approach if the implementation is relatively large, and if you rename the .cpp file to another extension, as to differentiate it from regular code.
只有当你#include "stack.cpp
在stack.hpp
. 如果实现相对较大,并且如果您将 .cpp 文件重命名为另一个扩展名,我只会推荐这种方法,以将其与常规代码区分开来。
回答by Konstantin Tenzin
Sometimes it is possible to have most of implementation hidden in cpp file, if you can extract common functionality foo all template parameters into non-template class (possibly type-unsafe). Then header will contain redirection calls to that class. Similar approach is used, when fighting with "template bloat" problem.
有时可能会将大部分实现隐藏在 cpp 文件中,如果您可以将通用功能 foo 所有模板参数提取到非模板类中(可能类型不安全)。然后标头将包含对该类的重定向调用。在解决“模板膨胀”问题时,使用了类似的方法。
回答by Mark Ransom
The problem is that a template doesn't generate an actual class, it's just a templatetelling the compiler how to generate a class. You need to generate a concrete class.
问题在于模板不会生成实际的类,它只是告诉编译器如何生成类的模板。您需要生成一个具体的类。
The easy and natural way is to put the methods in the header file. But there is another way.
简单而自然的方法是将方法放在头文件中。但还有另一种方式。
In your .cpp file, if you have a reference to every template instantiation and method you require, the compiler will generate them there for use throughout your project.
在您的 .cpp 文件中,如果您引用了您需要的每个模板实例化和方法,编译器将在那里生成它们以供整个项目使用。
new stack.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
}
回答by Macke
If you know what types your stack will be used with, you can instantiate them expicitly in the cpp file, and keep all relevant code there.
如果您知道您的堆栈将用于哪些类型,您可以在 cpp 文件中显式实例化它们,并将所有相关代码保留在那里。
It is also possible to export these across DLLs (!) but it's pretty tricky to get the syntax right (MS-specific combinations of __declspec(dllexport) and the export keyword).
也可以跨 DLL 导出这些(!),但要获得正确的语法(__declspec(dllexport) 和 export 关键字的特定于 MS 的组合)非常棘手。
We've used that in a math/geom lib that templated double/float, but had quite a lot of code. (I googled around for it at the time, don't have that code today though.)
我们已经在一个 math/geom 库中使用了它,该库模板化了 double/float,但有相当多的代码。(当时我在谷歌上搜索了它,不过今天没有那个代码。)
回答by Aaron
You need to have everything in the hpp file. The problem is that the classes aren't actually created until the compiler sees that they're needed by some OTHER cpp file - so it has to have all the code available to compile the templated class at that time.
您需要在 hpp 文件中包含所有内容。问题在于,在编译器发现某些其他 cpp 文件需要它们之前,实际上不会创建这些类 - 因此它必须具有当时可用于编译模板化类的所有代码。
One thing that I tend to do is to try to split my templates into a generic non-templated part (which can be split between cpp/hpp) and the type-specific template part which inherits the non-templated class.
我倾向于做的一件事是尝试将我的模板拆分为通用的非模板部分(可以在 cpp/hpp 之间拆分)和继承非模板类的特定于类型的模板部分。