1. 程式人生 > >STL原始碼剖析學習筆記(二)

STL原始碼剖析學習筆記(二)

iterator和traits程式設計技法(上)

STL的中心思想在於,將資料容器和演算法分開,彼此獨立設計,然後再用一個粘合劑將其黏在一起。這個粘合劑,就是iterator。
由於容器和演算法都是適應於泛型程式設計的,所以iterator也必須適應這方面的技術。

例如find(),它接受兩個迭代器和一個“搜尋目標”

template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& value) {
    while
(first != last && *first != value) ++first; return first; }

給定不同的迭代器,那麼find就可以針對不同的容器進行查詢操作。
迭代器是一種智慧指標,他針對指標中最常見的內容提領(dereference)和成員訪問(member access),這裡隱含的一個重要的訊息就是迭代器必須包含原生指標,即迭代器是原生指標的一種拓展。所以,迭代器最重要的就是過載operator*和operator->。

舉個例子,我們來模擬為一個單向連結串列list設計Iterator

連結串列

template
<typename T> class List { void insert_front(T value); void insert_end((T value); void display(std::ostream &os = std::cout) const; //....... private: ListItem<T>* _end; ListItem<T>* _front; long _size; }; template <typename T> class ListItem { public
: T value() const { return _value; } ListItem * next() const { return _next; } private: T _value; ListItem* _next; };

連結串列的Iterator實現

template <class Item>
struct ListIter
{
    Item * ptr;
    ListIter(Item *p = 0):ptr(p){ }//default creator
    Item& operator *() const { return *ptr; }
    Item* operator->() const { return ptr; }
    ListIter& operator++()
    { ptr = ptr->next() ;return *this; }
    ListIter operator++(int)
    { ListIter tmp = *this; ++*this; return tmp; }
    bool operator== (const ListIter& i) const
    { return ptr == i.ptr; }
    bool operator!=(const ListIter& i)const
    { return ptr != i.ptr; }
};

用iterator將List和find()連結起來:

void main()
{
    List<int> mylist;
    for(int i=0; i<5;++i){
        mylist.insert_front(i);
        mylist.insert_end(i+2);
    }
    mylist.display(); //4 3 2 1 0 2 3 4 5 6
    ListIter<ListItem<int>> begin(mylist.front());
    ListIter<ListItem<int>>end;
    ListIter<ListItem<int>>iter;

    iter = find(begin, end, 3);
    if (iter == end)
        cout<<"3 not found in list"<<endl;
    else
        cout<<"found."<<iter->value()<<endl;
}

由於find函式以 *iter!=value來檢測元素是否吻合,所以還要寫以下的全域性函式

template <typename T>
bool operator!=(const ListItem<T>& item, T n)
{ return item.value() != n; }

以上可以看出,這樣寫暴露了太多,首先為了製造 begin和end迭代器,我們暴露了ListItem;然後為了迭代器的++操作,我們又暴露了ListItem的next成員變數。也就是說,如果不是為了Iterator,ListItem應該是完全包含在List內部的,這個資料結構的節點不應該被暴露。
所以,我們乾脆讓容器實現自己的iterator好了
這樣看起來很合理。這就是為什麼STL裡,所有的容器都提供自己的專屬迭代器的原因。

現在,問題在於,如果我們寫迭代器裡面的一些演算法,不可避免的要用到迭代器所指的型別(其實還包括其他各種型別),如果我們要宣告一個這個型別的一個變數,這個型別我們怎麼取得?例如上例中的,迭代器所指之物的型別,就是int。C++並不支援用typeof()來判定一個元素的型別。即是用 typeid(),獲得也是型別名稱,不能拿來宣告變數。
解決方法:利用函式模板的引數堆導機制。
例如:

template<class I,class T>
void func_impl( I iter, T t )
{
    T tmp;//比方說,這個就是我們想要的int
};
template<class I>
inline
void func(I iter)
{
    func_impl(iter, *iter);
}
int main()
{
    int i;
    func(&i);
}

這段程式碼怎麼理解呢,就是說,當我們呼叫func的時候,會呼叫func_impl,而編譯器會對func_impl這個函式模板的引數型別進行自動推導,即推匯出來,T就是int。
套用到迭代器的話,func的引數只用填迭代器就行,func_impl函式會將迭代器所指物件的型別推匯出來,我們就可以直接用(例子中的T)。
剛才說迭代器所指型別,只是一種迭代器的型別,稱作迭代器的value_type。
上述函式模板引數推導機制,只能推導引數,如果value_type必須用作函式的返回值,則這個方法就不能用了,具體要用什麼方法。我們待續

下一篇:traits程式設計技法的思想,就是利用內嵌型別以及template引數推導功能,增強C++關於型別認證方面的能力。