模板和单独编译

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&lt;int&gt; 显式实例化它们。 以及如何编译 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 和 Array 对象,编译器实例化了 Array 类的两个不同定义,double 和 int 各一个,然后在 main.cpp 文件中实例化 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>;

【讨论】:

以上是关于模板和单独编译的主要内容,如果未能解决你的问题,请参考以下文章

(C++)模板分离编译面对的问题

C++ 模板

如何使用一个包含不同内容的模板和 grunt bake 创建单独的文件?

模板模板参数和模板别名:编译器错误?

传递派生模板类的向量

vue 将共享计算函数编译到单独的包中