2012-02-08 179 views
4

我想在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; 
} 

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 

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;}; 

我編譯d與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 

我知道這個問題是因爲我使用的模板,但我不知道如何解決它。

操作系統 - 視窗 編譯行 - g++ main.cpp Stack.h Stack.hpp -o main.exe

+0

可能的重複[爲什麼模板類的實現和聲明應該在同一個頭文件中?](http://stackoverflow.com/questions/3749099/why-should-the-implementation-和模板類的聲明) – 2012-02-08 17:04:27

回答

4

模板類需要有頭文件中的方法定義。

將您在.cpp文件中的代碼移動到標題中,或者創建一個名爲.impl.imp的文件,將代碼移動到標題中並將其包含在標題中。

編譯器需要知道方法定義以生成所有專業化的代碼。

在你問之前,不,沒有辦法將實現保持在頭之外。

+2

所以你想說當我使用模板時我不能使用單獨的編譯? – 2012-02-08 10:12:39

+0

@JordanBorisov。 – 2012-02-08 10:13:37

+0

@JordanBorisov見http://www.parashift.com/c++-faq-lite/templates.html - 第12條。 – 2012-02-08 10:17:28

0

我會說它會更實際一些,首先了解單獨編譯如何工作於正常(未被修改的)文件,然後理解g ++編譯器如何爲模板執行編譯。

首先,在普通文件中,當只包含聲明的頭文件包含在主文件中時,預處理程序會從聲明頭替換聲明並將其放到主文件中。然後,在預處理階段結束後,編譯器逐個編譯包含在.cpp文件中的純C++源代碼並將其轉換爲目標文件。此時編譯器不介意缺少的定義(函數/類),而且目標文件可以引用未定義的符號。因此編譯器只要編譯完好,就可以編譯源代碼。

然後,在鏈接階段,編譯器將多個文件鏈接在一起,並且在此階段鏈接器將在丟失/重複定義時產生錯誤。如果函數定義在其他文件中正確顯示,則鏈接器繼續執行,並且從主文件調用的函數成功鏈接到該定義並可以使用。

對於模板,事情的工作方式不同。這將是說明性的考慮一個例子,所以我挑選簡單:

考慮模板陣列類的頭文件中:

array.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); 
}; 

和相應的數組。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和另一個用於雙主文件。

的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> < int>和Array < double>對象被實例化時,編譯器實例化Array類的兩個不同定義,每個對象用於double和int,然後實例化main.cpp文件中的Array對象。

請注意,直到此時,main.cpp文件中仍缺少double> :: getLength()的int> :: getLength()和Array <的函數定義,但由於源代碼格式良好,編譯器毫無麻煩地編譯main.cpp文件。總而言之,到目前爲止,沒有區別黑白模板對象/函數編譯和非模板函數編譯。

與此同時編譯包含數組< T> :: getLength()和數組<的模板函數定義的array.cpp的代碼文件,但此時編譯器將擁有忘記了main.cpp中需要陣列< int>的::的getLength()和陣列<雙> ::的getLength()和將愉快地編譯代碼array.cpp而不產生由所需的函數定義的int和雙版本的任一實例main.cpp文件。 (請記住,編譯器分別編譯每個文件!)

這是在鏈接階段,由於缺少函數定義以及主文件所需的模板函數定義的雙重版本,可怕的模板錯誤開始彈出。在非模板聲明和定義的情況下,程序員確保在文件中定義查找函數,該文件可以與調用該函數的文件鏈接在一起。但是在模板的情況下,在編譯階段後執行的鏈接程序不能執行編譯器應該做的任務,即生成代碼,在這種情況下,模板函數的int和double類型爲

我們可以很容易得出這樣的結論:圍繞模板分離編譯的整個過程都是由於鏈接(即),如果所有的代碼都被正確寫入,在頭部聲明的類和函數並在另一個單獨的文件中定義)。解決此獲得的方法是:

  1. 定義在標題的類和函數文件本身,而不是單獨的文件中,這樣的頭文件中包含的主要文件時的內容,包括模板定義,從而導致相應的必須由編譯器定義的函數實例。

  2. 實例化,你知道你需要在模板中定義寫在不同的文件類型定義。這將直接鏈接到主文件中的函數調用。

  3. 另一種方式來解決這個問題是命名的.cpp文件,其中定義寫入.INL *文件(從e.g上面繪製,chagne array.cpp到array.inl); inl意味着內聯幷包含。inl文件從頭文件的底部。這與在頭文件中定義所有函數的結果相同,但有助於使代碼更清晰。

  4. 還有另一種方法,即#include .cpp文件與主文件中的模板化定義,我個人不喜歡因爲#include的非標準用法。