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

exs*_*ape 89 c++ linker templates header class

我在尝试编译一个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  
Run Code Online (Sandbox Code Playgroud)

这是我的代码:

stack.hpp:

#ifndef _STACK_HPP
#define _STACK_HPP

template <typename Type>
class stack {
    public:
            stack();
            ~stack();
};
#endif
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

main.cpp:

#include "stack.hpp"

int main() {
    stack<int> s;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

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

唯一合理的解决方案是将.cpp文件中的所有内容移动到.hpp文件中,并简单地包含所有内容,而不是作为独立的目标文件链接?这看起来非常难看!在这种情况下,我还不如回到我以前的状态,并重新命名stack.cpp,以stack.hpp和与它做.

小智 143

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

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

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

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

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

  • "必须要理解的是,模板类根本不是一个类" - 不是相反吗?类模板是一个模板.有时使用"模板类"代替"模板的实例化",并且它将是一个实际的类. (2认同)

Ben*_*oît 85

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

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

template class stack<int>;
Run Code Online (Sandbox Code Playgroud)

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

  • 在实践中,大多数人为此使用单独的cpp文件 - 类似于stackinstantiations.cpp. (7认同)
  • 实际上还有其他解决方案:http://www.codeproject.com/Articles/48575/How-to-define-a-template-class-in-ah-file-and-imp (3认同)
  • 实际上,正确的语法是`template class stack <int>;`. (3认同)

Sad*_*and 8

你可以这样做

// 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
Run Code Online (Sandbox Code Playgroud)

Daniweb已经讨论过这个问题

同样在FAQ中但使用C++导出关键字.

  • `include`ing`cpp`文件通常是个糟糕的主意.即使你有正当理由,这个文件 - 实际上只是一个美化的标题 - 应该被赋予一个`hpp`或一些不同的扩展名(例如`tpp`)来清楚地说明发生了什么,消除了混乱. makefile的目标是_actual_`cpp`文件等. (3认同)
  • @Abbas 因为扩展名`cpp`(或`cc`,或`c` 或其他)表明该文件是实现的一部分,生成的翻译单元(预处理器输出)是可单独编译的,并且内容的文件只编译一次。它并不表示该文件是界面的可重用部分,可以任意包含在任何地方。`#include` 一个 _actual_ `cpp` 文件会很快用多个定义错误填满你的屏幕,这是正确的。在这种情况下,由于 _is_ 有理由`#include` 它,`cpp` 只是错误的扩展选择。 (2认同)

Cha*_*via 6

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

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

  • 你的答案不正确.您可以从cpp文件中的模板类生成代码,前提是您知道要使用的模板参数.请参阅我的回答以获取更多信息 (3认同)
  • 是的,但是这需要更新.cpp文件的严格限制,并且每次引入使用模板的新类型时都要重新编译,这可能不是OP的想法. (2认同)