c++--模板編譯

如何組織編寫模板程序

前言
常遇到詢問使用模板到底是否容易的問題,我的回答是:“模板的使用是容易的,但組織編寫卻不容易”。看看我們幾乎每天都能遇到的模板類吧,如STL, ATL, WTL, 以及Boost的模板類,都能體會到這樣的滋味:接口簡單,操作複雜。

我在5年前開始使用模板,那時我看到了MFC的容器類。直到去年我還沒有必要自己編寫模板類。可是在我需要自己編寫模板類時,我首先遇到的事實卻是 “傳統”編程方法(在.h文件聲明,在.cpp文件中定義)不能用於模板。於是我花費一些時間來了解問題所在及其解決方法。

本文對象是那些熟悉模板但還沒有很多編寫模板經驗的程序員。本文只涉及模板類,未涉及模板函數。但論述的原則對於二者是一樣的。

問題的產生
通過下例來說明問題。例如在array.h文件中有模板類array:

// array.h
template <typename T, int SIZE>
class array
{
    T data_[SIZE];
    array (const array& other);
    const array& operator = (const array& other);
public:
    array(){};
    T& operator[](int i) {return data_[i];}
    const T& get_elem (int i) const {return data_[i];}
    void set_elem(int i, const T& value) {data_[i] = value;}
    operator T*() {return data_;}      
};   

然後在main.cpp文件中的主函數中使用上述模板:

// main.cpp
#include "array.h"

int main(void)
{
array<int, 50> intArray;
intArray.set_elem(0, 2);
int firstElem = intArray.get_elem(0);
int* begin = intArray;
}

這時編譯和運行都是正常的。程序先創建一個含有50個整數的數組,然後設置數組的第一個元素值爲2,再讀取第一個元素值,最後將指針指向數組起點。

但如果用傳統編程方式來編寫會發生什麼事呢?我們來看看:

將array.h文件分裂成爲array.h和array.cpp二個文件(main.cpp保持不變)

// array.h        
template <typename T, int SIZE>
class array
{
      T data_[SIZE];
      array (const array& other);
      const array& operator = (const array& other);
  public:
      array(){};
      T& operator[](int i);
      const T& get_elem (int i) const;
      void set_elem(int i, const T& value);
      operator T*();      
};        
// array.cpp
#include "array.h"

template<typename T, int SIZE> T& array<T, SIZE>::operator [](int i)
    {
    return data_[i];
    }

template<typename T, int SIZE> const T& array<T, SIZE>::get_elem(int i) const
    {
    return data_[i];
    }

template<typename T, int SIZE> void array<T, SIZE>::set_elem(int i, const T& value)
    {
    data_[i] = value;
    }
template<typename T, int SIZE> array<T, SIZE>::operator T*()
    {
    return data_;
    }

編譯時會出現3個錯誤。問題出來了:
爲什麼錯誤都出現在第一個地方?
爲什麼只有3個鏈接出錯?array.cpp中有4個成員函數。

要回答上面的問題,就要深入瞭解模板的實例化過程。

模板實例化
程序員在使用模板類時最常犯的錯誤是將模板類視爲某種數據類型。所謂類型參量化(parameterized types)這樣的術語導致了這種誤解。模板當然不是數據類型,模板就是模板,恰如其名:

編譯器使用模板,通過更換模板參數來創建數據類型。這個過程就是模板實例化(Instantiation)。
從模板類創建得到的類型稱之爲特例(specialization)。
模板實例化取決於編譯器能夠找到可用代碼來創建特例(稱之爲實例化要素,
point of instantiation)。
要創建特例,編譯器不但要看到模板的聲明,還要看到模板的定義。
模板實例化過程是遲鈍的,即只能用函數的定義來實現實例化。

再回頭看上面的例子,可以知道array是一個模板,array

// templateinstantiations.cpp                
#include "array.cpp"

template class array <int, 50>; // 顯式實例化

array<int, 50>類型不是在main.cpp中產生,而是在templateinstantiations.cpp中產生。這樣鏈接器就能夠找到它的定義。用這種方法,不會產生巨大的頭文件,加快編譯速度。而且頭文件本身也顯得更加“乾淨”和更具有可讀性。但這個方法不能得到惰性實例化的好處,即它將顯式地生成所有的成員函數。另外還要維護templateinstantiations.cpp文件。

第三種方法是在模板定義中使用export關鍵字,剩下的事就讓編譯器去自行處理了。當我在
Stroustrup的書中讀到export 時,感到非常興奮。但很快就發現VC 6.0不支持它,後來又發現根本沒有編譯器能夠支持這個關鍵字(第一個支持它的編譯器要在2002年底才問世)。自那以後,我閱讀了不少關於export 的文章,瞭解到它幾乎不能解決用包含模式能夠解決的問題。欲知更多的export關鍵字,建議讀讀Herb Sutter撰寫的文章。

結論
要開發模板庫,就要知道模板類不是所謂的”原始類型”,要用其它的編程思路。本文目的不是要嚇唬那些想進行模板編程的程序員。恰恰相反,是要提醒他們避免犯下開始模板編程時都會出現的錯誤。

這裏寫圖片描述

本文轉自:http://blog.csdn.net/look01/archive/2008/11/05/3228134.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章