模板和单独的编译

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

Luc*_*ore 6

模板类需要在头文件中包含方法定义.

.cpp标题中的文件中的代码移动到标题内,或者创建一个名为.implor 的文件.imp,然后在那里移动代码,并将其包含在标题中.

编译器需要知道方法定义以生成所有特化的代码.

在你提出之前,没有,没有办法将实现保持在标题之外.

  • @JordanBorisov:如果你知道将要传递给模板的类型,你可以使用`template class Stack <int>`显式地实例化它们. (3认同)
  • 所以你想说我在使用模板时不能使用单独的编译? (2认同)

ggu*_*lia 6

我会说首先了解单独编译如何为普通(非模板)文件工作,然后了解 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 类型

有办法解决这个问题

看完整个故事,人们可以很容易地得出结论,围绕模板单独编译的整个大惊小怪是由于链接(即)如果所有代码都正确编写,类和函数在头文件中声明并在另一个单独的文件中定义)。解决这个问题的方法是:

  1. 在头文件本身而不是在单独的文件中定义类和函数,这样当头文件的内容包含在主文件中时,包括模板化定义,这些定义导致编译器定义必要函数的适当实例。

  2. 在编写模板定义的单独文件中实例化您知道需要的类型定义。这将直接链接到主文件中的函数调用。

  3. 解决此问题的另一种方法是将定义写入.inl*文件的.cpp文件命名(从上面绘制的示例中,将 array.cpp 更改为 array.inl);inl 表示内联并包含头文件底部的 .inl 文件。这与在头文件中定义所有函数的结果相同,但有助于使代码更简洁一些。

  4. 还有另一种方式,即#include .cpp 文件在主文件中带有模板化定义,我个人不喜欢这种方式,因为#include 的使用非标准。