将模板化的C++类分割成.hpp和.cpp文件,这是否可行?

111

我尝试编译一个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:

=>

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:

:主要的 C++ 源代码文件。
#include "stack.hpp"

int main() {
    stack<int> s;

    return 0;
}

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

这个问题 的答案没有帮助,因为我已经按照它说的做了。
这个或许有点帮助,但我不想把每个方法都移动到 .hpp 文件中,我不应该这样做,对吗?

唯一合理的解决方案是将 .cpp 文件中的所有内容都移到 .hpp 文件中,并简单地包含所有内容,而不是链接到一个独立的目标文件中吗?那看起来太丑陋了!如果那样的话,我可能还不如回到之前的状态,将 stack.cpp 改名为 stack.hpp 就行了。


有两种很好的解决方案,可以让你真正地隐藏代码(在二进制文件中)或保持代码的清晰。虽然在第一种情况下需要减少通用性。这里解释了原因:https://dev59.com/O3RB5IYBdhLWcg3w1Kr0 - Sheric
1
显式模板实例化是减少模板编译时间的一种方法:https://dev59.com/2HE95IYBdhLWcg3wWMdQ - Ciro Santilli OurBigBook.com
16个回答

1

你需要在hpp文件中包含所有内容。问题在于,类实际上直到编译器看到它们被其他cpp文件需要时才会创建 - 因此必须在那个时候有所有可用的代码来编译模板类。

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


0

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

#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

就风格而言,我不喜欢这个建议,但它可能适合你。


1
被赞扬的第二个头文件至少应该有一个不同于 cpp 的扩展名,以避免与_实际_源文件混淆。常见建议包括 tpptcc - underscore_d

0

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

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

        TClass<int> myObj;

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

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


0

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


0

「export」关键字是将模板实现与模板声明分离的方式。这在C++标准中引入,但没有现有的实现。在过程中,只有少数编译器实际上对其进行了实现。详细信息请阅读Inform IT关于export的文章


2
这几乎是一个仅包含链接的回答,而且那个链接已经失效了。 - underscore_d

-3

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接