class - 将模板化的C ++类拆分为.hpp / .cpp文件 - 是否可能?

我在尝试编译C ++模板类时遇到错误,该类在.hpp.cpp文件之间分割:

$ 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;
}

.cpp当然是正确的:符号不在.hpp中。

这个问题的答案没有用,正如我所说的那样。
这个可能会有所帮助,但我不想将每个方法都移到.cpp文件中 - 我不应该这样做,我应该吗?

.cpp文件中的所有内容移动到.hpp文件是唯一合理的解决方案,只需包含所有内容,而不是作为独立目标文件链接? 这看起来非常难看! 在那种情况下,我不妨恢复到我以前的状态并将stack.cpp重命名为stack.hpp并完成它。

exscape asked 2019-09-10T14:23:42Z
16个解决方案
137 votes

不可能在单独的cpp文件中编写模板类的实现并进行编译。 所有这些方法,如果有人声称,是模拟单独的cpp文件的使用的解决方法,但实际上如果你打算编写模板类库并使用header和lib文件分发它来隐藏实现,那根本不可能。

要知道原因,让我们看一下编译过程。 永远不会编译头文件。 它们只是经过预处理。 然后用预先编译的cpp文件对预处理的代码进行分组。 现在,如果编译器必须为对象生成适当的内存布局,则需要知道模板类的数据类型。

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

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

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

Sharjith N. answered 2019-09-10T14:24:31Z
80 votes

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

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

template class stack<int>;

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

Benoît answered 2019-09-10T14:25:13Z
8 votes

你可以这样做

// 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 ++导出关键字。

Sadanand answered 2019-09-10T14:25:58Z
6 votes

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

您可以做的最好的事情是将您的函数实现放在“.tcc”或“.tpp”文件中,并将#tlude文件放在.hpp文件的末尾。 然而,这仅仅是装饰性的; 它仍然与在头文件中实现所有内容相同。 这只是您使用模板支付的价格。

Charles Salvia answered 2019-09-10T14:26:31Z
3 votes

我相信尝试将模板代码分成标题和cpp有两个主要原因:

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

另一个是减少编译时间。

我目前(一如既往)编码模拟软件与OpenCL一起使用,我们希望保留代码,以便根据硬件功能可以根据需要使用float(cl_float)或double(cl_double)类型运行。 现在这是在代码开头使用#define REAL完成的,但这不是很优雅。 更改所需的精度需要重新编译应用程序。 由于没有真正的运行时类型,我们暂时不得不忍受这种情况。 幸运的是OpenCL内核是编译运行时,简单的sizeof(REAL)允许我们相应地改变内核代码运行时。

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

Meteorhead answered 2019-09-10T14:27:26Z
2 votes

有时可以将大部分实现隐藏在cpp文件中,如果可以将所有模板参数的常用功能提取到非模板类(可能是类型不安全)。 然后header将包含对该类的重定向调用。 当与“模板膨胀”问题作斗争时,使用类似的方法。

Konstantin Tenzin answered 2019-09-10T14:27:54Z
2 votes

如果您知道堆栈将与哪种类型一起使用,您可以在cpp文件中明确地实例化它们,并保留所有相关代码。

也可以跨DLL(!)导出这些,但是使语法正确(特定于MS的__declspec(dllexport)和export关键字的组合)非常棘手。

我们在一个模拟double / float的math / geom lib中使用了它,但是有很多代码。 (我当时用Google搜索,但今天没有这个代码。)

Macke answered 2019-09-10T14:28:39Z
2 votes

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

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

在.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
}
Mark Ransom answered 2019-09-10T14:29:25Z
1 votes

你需要拥有hpp文件中的所有内容。 问题是在编译器发现某些OTHER cpp文件需要它们之前,实际上并没有创建类 - 所以它必须拥有所有可用的代码来编译模板化的类。

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

Aaron answered 2019-09-10T14:29:57Z
1 votes

仅当您#include "stack.cppstack.hpp结束时才会这样做。我只推荐这种方法,如果实现相对较大,并且将.cpp文件重命名为另一个扩展名,以区别于常规代码。

lyricat answered 2019-09-10T14:30:26Z
1 votes

这是一个相当古老的问题,但我认为观看Arthur O'Dwyer在cppcon 2016上的演讲很有意思。很好的解释,很多主题涵盖,必须观看。

FreeYourSoul answered 2019-09-10T14:30:52Z
0 votes

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

ChadNC answered 2019-09-10T14:31:19Z
0 votes

另一种可能性是做以下事情:

#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

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

luke answered 2019-09-10T14:31:52Z
0 votes

'export'关键字是将模板实现与模板声明分开的方法。 这是在C ++标准中引入的,没有现有的实现。 在适当的时候,只有几个编译器实际实现了它。 有关导出的Inform IT文章,请阅读深入信息

Shailesh Kumar answered 2019-09-10T14:32:19Z
0 votes

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

2)非模板类具有在.h和.cpp文件中具体和具体定义的所有变量。 因此,在编译/转换生成对象/机器代码之前,编译器将需要有关类中使用的所有数据类型的信息。在类的用户实例化传递所需数据类型的对象之前,模板类没有关于特定数据类型的信息:

        TClass<int> myObj;

3)仅在此实例化之后,编译器生成模板类的特定版本以匹配传递的数据类型。

4)因此,.cpp不能在不知道用户特定数据类型的情况下单独编译。 所以它必须作为源代码保留在“.h”中,直到用户指定所需的数据类型然后,它可以生成为特定的数据类型然后编译

Aaron01 answered 2019-09-10T14:33:11Z
-3 votes

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

Ahmad answered 2019-09-10T14:33:39Z
translate from https://stackoverflow.com:/questions/1724036/splitting-templated-c-classes-into-hpp-cpp-files-is-it-possible