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++關於型別認證方面的能力。