拆分模板C ++类到.HPP / .cpp文件 - 这可能吗?拆分模板C ++类到.HPP / .c

2019-05-08 16:17发布

我得到的错误尝试编译这是一间拆分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;
}

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

在回答这个问题没有帮助,因为我已经在做,因为它说。
这其中可能有帮助,但我不希望移动每个单独的方法进入.hpp文件我不应该来,我应该?

是唯一合理的解决方案,以在移动一切.cpp文件到.hpp文件,只是包括了一切,而不是链接作为一个独立的对象文件? 这似乎非常难看! 在这种情况下,我还不如回到我以前的状态,并重新命名stack.cppstack.hpp ,并用它做。

Answer 1:

这是不可能的编写模板类的实现在一个单独的CPP文件和编译。 所有的方式来做到这一点,如果有人声称,变通方法来模拟不同的CPP文件的使用情况,但实际上,如果你打算写模板类库,并用头和lib文件分发到隐藏实现,那简直是不可能的。

要知道为什么,让我们来看看编译过程。 头文件从不编译。 它们只进行预处理。 预处理代码,然后用这实际上是编译cpp文件棍。 现在,如果编译器生成的对象适当的内存布局,它需要知道模板类的数据类型。

实际上,它必须理解的是,模板类不是一个类都而是一类是由编译器在编译时正从参数的数据类型的信息后,生成的声明和定义的模板。 只要存储器布局不能被创建时,不能产生用于定义方法的指令。 记住类方法的第一个参数是“本”操作。 所有类的方法被转换成与名字粉碎并作为其操作上的对象的第一参数各个方法。 该“这”的说法是,实际上讲述其柜面的模板类是编译器不可用,除非用户用实例化一个有效的类型参数的对象物体的大小。 在这种情况下,如果你把方法定义在一个单独的cpp文件,并尝试编译它本身不会与类信息产生的目标文件。 编译不会失败,它会生成对象文件,但它不会生成目标文件的模板类的任何代码。 这就是为什么链接器无法找到目标文件的符号和构建失败。

那么什么是隐藏重要的实现细节的选择吗? 大家都知道后面的接口和实现分离的主要目标是以二进制形式隐藏实现细节。 这是你必须单独的数据结构和算法。 您的模板类必须只占数据结构而不是算法。 这使您可以隐藏单独的非模板化的类库更有价值的实施细则,类内这将在模板类工作,或者只是用它们来保存数据。 模板类实际上包含更少的代码来分配,获取和设置数据。 工作的其余部分将通过算法类来完成。

我希望这次讨论将是有益的。



Answer 2:

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

添加以下代码在stack.cpp的结束,它会工作:

template class stack<int>;

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



Answer 3:

你可以这样做

// 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

此外,在回答问题 ,但使用C ++出口关键字。



Answer 4:

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

你能做的最好的是把你的函数实现在“.tcc”或“.tpp”的文件,并在你的.HPP文件的末尾#包括了.tcc文件。 然而,这只是一种表象; 它仍然是一样实现在头文件中的一切。 这仅仅是你付使用模板的价格。



Answer 5:

我相信对于想单独模板的代码放到一个头部和一段cpp的原因主要有两个:

一个是纯粹的优雅。 我们都喜欢写代码,WASY阅读,管理和可重复使用更高版本。

另一种是减少的编译时间。

我目前(一如既往)编码与OpenCL的一起选择仿真软件,我们想保持代码,以便它可以使用浮动(cl_float)或双(cl_double)类型,需要根据硬件能力运行。 现在,这是使用在代码的开头使用#define REAL做,但是这是不是很优雅。 改变期望的精度要求重新编译应用程序。 由于没有实际运行时类型,我们有这样的生活暂时。 幸运的是OpenCL内核编译运行时,和一个简单的sizeof(REAL)使我们能够相应地改变内核代码运行。

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



Answer 6:

有时,可能有大部分实现隐藏在CPP文件,如果你可以提取公共的功能foo的所有模板参数代入非模板类(可能是类型不安全)。 然后头将包含重定向调用这个类。 类似的方法被使用,“模板膨胀”的问题时,战斗。



Answer 7:

如果你知道什么类型的筹码将被使用,您可以在cpp文件expicitly实例化它们,并保留所有相关的代码出现。

也可以导出这些跨越的DLL(!),但它是相当棘手的语法正确的((DLLEXPORT)和出口关键字__declspec的MS-特定组合)。

我们使用的是在模板双/浮动,但有相当多的代码数学/ GEOM库。 (我用Google搜索周围它的时候,没有今天的代码虽然)。



Answer 8:

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

最简单和自然的方式是把这些方法在头文件。 但还有另一种方式。

在您的.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
}


Answer 9:

你需要有在HPP文件的一切。 问题是,这些类之前不会真正创建编译器看到,他们正在通过一些其他的CPP文件所需 - 所以它必须有所有可用编译模板类当时的代码。

有一两件事,我倾向于做的是尽量我的模板分割成一个通用的非模板组成部分,它继承了非模板类的特定类型的模板部分(可CPP / HPP之间拆分)。



Answer 10:

只有当你#include "stack.cpp在结束stack.hpp 。我只推荐这种方法如果实现是比较大的,如果你重命名.cpp文件到另一个分机,以与普通的代码区分开来。



Answer 11:

这是一个很老的问题,但我认为这是值得关注的呈现在cppcon 2016阿瑟·奥德怀尔 。 很好的解释,很多题目涉及的,一个必须警惕。



Answer 12:

因为在需要时模板编译,这会强制对多文件项目的限制:一个模板类或函数的实现(定义)必须在同一个文件作为它的声明。 这意味着,我们不能在一个单独的头文件中的接口分开,我们必须在使用该模板的任何文件接口和实现。



Answer 13:

另一种可能性是做这样的事情:

#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

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



Answer 14:

“导出”关键字是从模板声明分离出模板实现的方式。 这是在C ++标准中引入无现有的实现。 在适当的时候只有几个编译器的实际执行它。 深入阅读信息的通知IT出口品



Answer 15:

1)记住主要原因.H分离和.cpp文件是隐藏类实现的可链接到用户的代码,其中包括类的.H单独编译的OBJ代码。

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

        TClass<int> myObj;

3)只有这个实例之后,编译生成的模板类的特定版本,以配合传递的数据类型。

4)因此,的.cpp不能单独无需知道用户特定数据类型编译。 因此它具有留作为源代码“的.h”内,直到用户指定所需的数据类型,然后,它可以产生一个特定的数据类型,然后编译



Answer 16:

我与Visual Studio 2010的工作,如果你想分割你的文件的.h和.cpp,包括你的CPP头在.h文件的末尾



文章来源: Splitting templated C++ classes into .hpp/.cpp files--is it possible?