模板和单独编译
Posted
技术标签:
【中文标题】模板和单独编译【英文标题】:Templates and separate compilation 【发布时间】:2012-02-29 18:19:39 【问题描述】:我想用 C++ 编写一个单独编译的程序,我写了这个:
main.cpp
#include <iostream>
#include "Stack.h"
using namespace std;
int main(int argc,char* argv[])
Stack<int> st;
st.push(1);
return 0;
堆栈.h
#ifndef _STACK_H
#define _STACK_H
template<typename T>
class Stack
private:
struct Node
Node* _prev;
T _data;
Node* _next;
;
int _size;
Node* _pos;
public:
Stack();
T pop();
void push(T const &el);
int getSize() const;
;
#endif
堆栈.hpp
#include "Stack.h"
#include <malloc.h>
template <typename T>
Stack<T>::Stack()
_size = 0;
_pos = (Node*)malloc(sizeof(Node));
_pos->_prev = NULL;
_pos->_next = NULL;
template <typename T>
T Stack<T>::pop()
if (_size == 0)
return NULL;
T tmp = _pos->_data;
if (_pos->_prev == NULL)
free(_pos);
else
_pos->_prev->_next = _pos->_next;
if (_pos->_next != NULL)
_pos->_next->_prev = _pos->_prev;
free(_pos);
_size--;
return tmp;
template <typename T>
void Stack<T>::push(T const &el)
Node* n = (Node*)malloc(sizeof(Node));
_pos->_next = n;
n->_prev = _pos;
n->_data = *el;
_pos = n;
_size ++;
template<typename T>
int Stack<T>::getSize() const return _size;;
我用 g++ 编译了程序,我得到了这个错误:
ccyDhLTv.o:main.cpp:(.text+0x16): undefin
ed reference to `Stack<int>::Stack()'
ccyDhLTv.o:main.cpp:(.text+0x32): undefin
ed reference to `Stack<int>::push(int const&)'
collect2: ld returned 1 exit status
我知道问题是因为我正在使用模板,但我不知道如何解决它。
操作系统 - Windows
编译行——g++ main.cpp Stack.h Stack.hpp -o main.exe
【问题讨论】:
Why should the implementation and the declaration of a template class be in the same header file? 的可能重复项 【参考方案1】:模板类需要在头文件中有方法定义。
将.cpp
文件中的代码移动到标题中,或创建一个名为.impl
或.imp
的文件,将代码移到那里,并将其包含在标题中。
编译器需要知道方法定义才能为所有特化生成代码。
在你问之前,不,没有办法将实现保留在标题之外。
【讨论】:
所以你想说我用模板的时候不能单独编译? @JordanBorisov 见 parashift.com/c++-faq-lite/templates.html - 第 12 节。 @JordanBorisov 就是这样。这就是模板的意义所在。如果你想使用它们,那就是你需要这样做。 @JordanBorisov:如果您知道要传递到模板中的类型,您可以使用template class Stack<int>
显式实例化它们。
以及如何编译 Stack.impl 文件?【参考方案2】:
我会说首先了解单独编译如何适用于普通(非模板)文件,然后了解 g++ 编译器如何处理模板会更实用。
首先在普通文件中,当仅包含声明的头文件在主文件中被#include 时,预处理器会替换头中的声明并将其放入主文件。然后在预处理阶段结束后,编译器对 .cpp 文件中包含的纯 C++ 源代码进行一一编译,并将其翻译成目标文件。此时编译器不介意缺少定义(函数/类),并且目标文件可以引用未定义的符号。因此,只要源代码格式正确,编译器就可以编译源代码。
然后在链接阶段,编译器将多个文件链接在一起,并且在此阶段,链接器将在缺少/重复定义时产生错误。如果函数定义正确存在于另一个文件中,则链接器继续进行,并且从主文件调用的函数成功链接到定义并可以使用。
对于模板,情况有所不同。举个例子来说明一下,所以我选择一个简单的例子:
考虑模板数组类的头文件:
数组.h
#ifndef _TEMPLATE_ARRAY_H_
#define _TEMPLATE_ARRAY_H_
template <class T>
class Array
private:
T *m_list;
int m_length;
public:
Array() //default constructor
m_list = nullptr;
m_length = 0;
Array(int length)
m_list = new T[length];
m_length = length;
~Arrary()
delete[] m_list;
m_list = nullptr;
//undefined functions
int getLength();
T getElement(const int pos);
;
以及对应的array.cpp文件:
include "array.h"
template <class T>
array<T>::getLength()
return m_length;
template <class T>
T Array<T>::getElement(const int pos)
return m_list[pos];
现在考虑创建模板化对象数组的两个实例的主文件,一个用于 int,另一个用于 double。
main.cpp
#include "array.h"
#include <iostream>
int main()
Array<int> int_array;
Array<double> double_array;
std::cout << int_array.getLength() <<"\n";
std::cout << double_array.getLength() << "\n";
当这段代码被编译时,预处理器首先像往常一样将模板声明从头文件复制到主文件。因为在主文件中实例化了 Array
注意到目前为止,main.cpp 文件中仍然缺少 Array::getLength() 和 Array::getLength() 的函数定义,但由于源代码格式正确,编译器可以编译main.cpp 文件没有任何麻烦。简而言之,到目前为止,黑白模板化对象/函数编译和非模板化函数编译没有区别。
同时编译了包含 Array::getLength() 和 Array::getElement() 的模板函数定义的 array.cpp 的代码文件,但此时编译器会忘记那 main.cpp 需要 Array::getLength() 和 Array::getLength() 并且会愉快地编译代码 array.cpp 而不为 main 所需的函数定义的 int 和 double 版本生成任何实例.cpp 文件。 (记住编译器会分别编译每个文件!)
在链接阶段,由于缺少主文件所需的 int 和 double 版本的模板函数定义的函数定义,可怕的模板错误开始弹出。在非模板声明和定义的情况下,程序员确保在一个文件中定义寻找的函数,该文件可以与调用函数的文件链接在一起。但是在模板的情况下,在编译阶段之后执行的链接器不能完成编译器应该做的任务,即生成代码,在这种情况下是模板函数的 int 和 double 类型
有办法解决这个问题
看完整个故事,很容易得出结论,围绕模板单独编译的整个大惊小怪是由于链接(即,如果所有代码都正确编写,类和函数在标题中声明并在另一个单独的文件中定义) .解决这个问题的方法是:
在头文件本身而不是在单独的文件中定义类和函数,以便在包含在主文件中时,头文件的内容包括模板化定义,这些定义导致必要函数的适当实例由编译器。
在编写模板定义的单独文件中实例化您知道需要的类型定义。然后,这将直接链接到主文件中的函数调用。
解决此问题的另一种方法是将定义写入 .inl* 文件的 .cpp 文件命名(来自上面绘制的示例,chagne array.cpp 到 array.inl); inl 表示内联并包含头文件底部的 .inl 文件。这与在头文件中定义所有函数产生相同的结果,但有助于保持代码更简洁。
还有另一种方式,即主文件中带有模板定义的#include .cpp 文件,由于#include 的非标准用法,我个人不喜欢这种方式。
【讨论】:
【参考方案3】:拥有模板和单独编译绝对是可能的,但前提是您事先知道模板将被实例化的类型。
头文件sep_head.h
:
template< typename T >
class sep
public:
sep() ;
void f();
;
主要:
#include "sep_head.h"
int main()
sep<int> s; s.f();
sep<double> sd; sd.f();
sep<char> sc; sc.f();
return 0;
实施:
#include "sep_head.h"
template< typename T >
void sep<T>::f()
template class sep<int>;
template class sep<double>;
template class sep<char>;
【讨论】:
以上是关于模板和单独编译的主要内容,如果未能解决你的问题,请参考以下文章