Jor*_*sov 5 c++ compiler-construction templates class
我想用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;
}
Run Code Online (Sandbox Code Playgroud)
Stack.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
Run Code Online (Sandbox Code Playgroud)
Stack.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;};
Run Code Online (Sandbox Code Playgroud)
我用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
Run Code Online (Sandbox Code Playgroud)
我知道问题是因为我使用模板但我不知道如何解决它.
OS - Windows编译行 - g++ main.cpp Stack.h Stack.hpp -o main.exe
模板类需要在头文件中包含方法定义.
将.cpp标题中的文件中的代码移动到标题内,或者创建一个名为.implor 的文件.imp,然后在那里移动代码,并将其包含在标题中.
编译器需要知道方法定义以生成所有特化的代码.
在你提出之前,没有,没有办法将实现保持在标题之外.
我会说首先了解单独编译如何为普通(非模板)文件工作,然后了解 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);
};
Run Code Online (Sandbox Code Playgroud)
和相应的 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]; }
Run Code Online (Sandbox Code Playgroud)
现在考虑主文件,其中创建了模板化对象数组的两个实例,一个用于 int,另一个用于 double。
主程序
#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";
}
Run Code Online (Sandbox Code Playgroud)
编译这段代码时,预处理器首先像往常一样将模板声明从头文件复制到主文件中。因为在主文件Array<int>和Array<double>对象被实例化,编译器实例化两个不同定义的Array类,double和int各一个,然后在main.cpp文件中实例化Array对象。
请注意,直到此时,main.cpp 文件中仍然缺少 Array< int >::getLength() 和 Array< double >::getLength() 的函数定义,但由于源代码格式良好,编译器会编译 main.cpp 文件。 cpp 文件没有任何麻烦。简而言之,到目前为止,黑白模板化对象/函数编译和非模板化函数编译没有区别。
与此同时,包含 Array< T >::getLength() 和 Array< T >::getElement() 模板函数定义的 array.cpp 代码文件被编译,但此时编译器可能已经忘记了 main。 cpp 需要 Array< int >::getLength() 和 Array< double >::getLength() 并且很乐意编译代码 array.cpp 而不为 main.cpp 文件所需的函数定义的 int 和 double 版本生成任何实例. (请记住,编译器分别编译每个文件!)
在链接阶段,由于缺少主文件所需的 int 和双版本模板函数定义的函数定义,可怕的模板错误开始出现。在非模板声明和定义的情况下,程序员确保在一个文件中定义寻找的函数,该文件可以与调用该函数的文件链接在一起。但是在模板的情况下,在编译阶段之后执行的链接器不能完成编译器应该做的任务,即生成代码,在这种情况下是模板函数的 int 和 double 类型
有办法解决这个问题
看完整个故事,人们可以很容易地得出结论,围绕模板单独编译的整个大惊小怪是由于链接(即)如果所有代码都正确编写,类和函数在头文件中声明并在另一个单独的文件中定义)。解决这个问题的方法是:
在头文件本身而不是在单独的文件中定义类和函数,这样当头文件的内容包含在主文件中时,包括模板化定义,这些定义导致编译器定义必要函数的适当实例。
在编写模板定义的单独文件中实例化您知道需要的类型定义。这将直接链接到主文件中的函数调用。
解决此问题的另一种方法是将定义写入.inl*文件的.cpp文件命名(从上面绘制的示例中,将 array.cpp 更改为 array.inl);inl 表示内联并包含头文件底部的 .inl 文件。这与在头文件中定义所有函数的结果相同,但有助于使代码更简洁一些。
还有另一种方式,即#include .cpp 文件在主文件中带有模板化定义,我个人不喜欢这种方式,因为#include 的使用非标准。