CUJ:標準庫:定義iterator和const iterator (轉)
The Standard Librarian: Defining Iterators and Const Iterators
Matt Austern:namespace prefix = o ns = "urn:schemas--com::office" />
http://www.cuj.com/experts/1901/austern.htm?topic=experts
------------------------------------------------------------------------------------------------------------------------
寫一個iterator並不難,並且它是擴充套件C++標準執行庫的一個自然方式。但如果你想做正確,還是一些應該知道的關鍵點的。
標準C++執行庫被設計得可擴充套件的:標準演算法(如reverse()和partition())對預定義的容器(如vector和list)進行操作,並且它們也可以操作於自定義的提供了適當iterator的資料結構。對執行庫的高效使用包括對它的擴充。
現如今,iterator對絕大多數的C++員都不陌生了。Iterator抽象了指標的絕大部分基本特徵:forward iterator指向序列中的某個,而且它能進行增量運算以指向序列中的下一個元素。(更強的iterator範疇,bidirectional iterator和ran iterator提供了額外的遍歷序列的方法。更弱的iterator範疇則對絕大多數的資料結構都是不合適的。)
每iterator都有一個範疇型別category_type(對forward iterator來說則是std::forward_iterator_tag),一個value_type (它所指向的物件的型別),一個difference_type(表示序列中兩個元素間距離的一個整數型別),以及一個pointer_type和reference_type(指向iterator的value_type的指標和引用)。這些型別都可透過std::iterator_traits來訪問;當你定義自己的iterator類時,提供它們的最容易的方法是提供巢狀的typedefs: iterator_category,value_type,difference_type,pointer,和reference。
所謂forward iterator就是任何滿足C++標準§24.1.3中的需求的型別;標準的那個小節告訴了你要定義什麼成員和過載哪些運算子。一旦你已經知道iterator需要掌握的資訊(以使得它能指向一個元素和找到其下一個元素),定義一個forward iterator只不過是對這些成員函式進行填空。
配對的Iterator
使問題變複雜的一個因素是通常定義一個iterator類是不夠的。你可能需要定義兩個iterator類,一個允許修改它所指向的物件(*i返回物件的引用),而另一個不允許(*i返回一個const的引用)。執行庫預定義的容器類這麼做了:舉例來說,std::list類,有一個巢狀型別 iterator和另外一個不同的巢狀型別const_iterator;後者可以用來遍歷const std::list。list
配對的iterator在使用者自定義型別中如同在標準執行庫預定義型別中一樣常見。舉例來說,假設你正在定義一個簡單的單向連結串列類。你可能這樣開始:
template
struct slist_node {
T val;
slist_node* next;
slist_node
(const T& t, slist_node* p)
: val(t), next(p) { }
};
template
slist_node
...
};
供slist用的iterator類同樣簡單:
template
struct slist_iterator {
typedef std::forward_iterator_tag
iterator_category;
typedef T value_type;
typedef std::ptrdiff_t
difference_type;
typedef T& reference;
typedef T* pointer;
slist_iterator(slist_node
: p(x) { }
slist_iterator
(const slist_iterator& i)
: p(i.p) { }
reference operator*() const
{ return p->val; }
pointer operator->() const
{ return &(p->val); }
slist_iterator& operator++() {
p = p->next;
return *this;
}
slist_iterator operator++(int) {
slist_iterator tmp(*this);
++*this;
return tmp;
}
slist_node
};
我們該如何定義相應的const iterator?我們可以定義一個獨立的 slist_const_iterator類,但程式碼重複造成浪費和易於出錯。將slist_iterator變成slist_const_iterator時,變化是極小的:
l 申明p的型別為const slist_node
l 申明pointer和reference為const T*和const T&。
l 定義一個轉換建構函式,它接受一個slist_iterator型別的引數。
這並不妨礙定義單個類來同時取代slist_iterator和slist_const_iterator。我們將iterator定義為接受一些額外模板引數,這些引數決定它是否為一個const iterator。我們給這個類一個帶非const版本的引數的建構函式;一種情況下它將成為一個複製建構函式,而另一種情況下它將成為一個轉換建構函式。另外二個差異只涉及用一個型別代替另外一個,因此很容易將它們封裝入模板引數。
最後:那些額外的模板引數應該看起來象什麼?在我的書中[注1],我建議pointer和reference型別作為模板引數顯式傳入。那個方法是可以的,但是它造成了多少有些笨重的型別名稱;有一個整潔的解決方案。我們可以只提供一個額外模板引數,一個布林標誌,以決定我們是否正在定義const iterator,然後使用一點小技巧:“編譯期的? : 操作”以根據此標誌選擇一個型別或另一個。這展示於Listing 1。
等於比較
我們還沒有定義一個相等操作。這中間還隱藏著一個問題,而你甚至能在一些標準執行庫預定義的iterator中發現它。試著編譯這一個程式:
#include
int main() {
std::deque
std::deque
while (d.end() != i)
++d;
}
程式沒有做任何事,但問題不在於這一點。重要的是,對很多現存的執行庫的實作,它甚至不能編譯。那些實作有嗎?不一定;i的型別是deque
你可能想知道這怎麼會成為問題。畢竟,我們不是已經說了容器的 iterator型別總能被轉換到它的const iterator型別嗎?如果d.begin()能轉換成deque<>::const_iterator,那麼為什麼不能比較他們?
問題是有許多的不同方法來定義iterator的相等操作;如果它們是按兩種最顯而易見的方法來定義的,容器的iterator和const iterator型別之間的比較將不能工作。
首先,假設operator==()被定義為成員函式。這不足夠好。如果i是 deque<>::const_iterator,而j是deque<>::iterator,那麼i == j可以工作而j == i不能。很容易明白不對稱的原因:成員函式天生就是不對稱的。a.f(b)這樣的(或,對本例,j.operator==(i))特定類的成員函式;轉換隻發生在函式的引數上。
再明白不過了,於是,你的下個想法可能是定義operator==()為非成員函式。很不幸,這樣作更糟!一個簡單的程式舉例說明了問題:
template
template
B() { }
B(A
};
template
void f(B
int main() {
A
B
// convertible to B
f(a, b); // Doesn’t work
}
A能轉換到B還不足夠。如果f不是模板,不會有問題:會應用使用者自定義的A
我們不能申明operator==()為成員函式,也不能是非成員模板函式。看起來我們需要申明一系列的非模板函式,對應於每個可能的iterator類例項。這是一個奇怪的需求,因為一系列引數化的函式正是模板的功能,但最奇怪的是它實際上是可能的。
它之所以可能,是因為類别範本中友元申明上的一個含糊的[注5](WQ注,參看Herb Sutter的文章《Sutter's Mill: Befriending Templates》,CSDN上亦有我譯的中文版)。你能顯式申明友元函式是一個模板(的例項)。然而,如果你沒有,並且如果它不匹配於已經被申明的函式模板,那麼編譯器將假設它是一個普通的非模板函式。舉例來說:
template
template
// f is a function template
friend void f
// g is a function template
friend void ::g(T);
// h is a non-template function
friend void h(T);
};
通常這隻會造成麻煩:正常情況下你期望編譯期將h這樣的東西認為是函式模板的申明,於是你不得不記住將它按編譯器認為的方式申明。然而,有時候,這個怪異之事還真的有用。如果在友元宣告時同時定義這個函式,並且申明它為一個非模板友元,你將會得到一系列的非模板函式--正是我們在iterator的相等操作上所需要的。
template
friend void f(T) { } // f is a non-template function
};
slist_iterator的完全的定義,包括相等比較,見於Listing 2。
總結
當你寫一個容器或象容器的東西時,通常定義一個iterator類是不夠的。你需要定義配對的iterator和const iterator類。定義這樣的配對類會帶來一些實現上的問題,這些問題是在只定義單個iterator類時是沒有的。
iterator/const iterator這兩個類必須具有相同的iterator category,defference type和value_type;iterator類必須能轉換到const iterator類,但不能反過來。你將iterator和const iterator定義為一個類,透過增加額外的模板引數並使用choose模板(或類似的東西)來定義適當的巢狀型別。如果你正在使用預定義的容器類(string,vector,list,deque,set,map,mulitset,multimap),應該避免對它的一個iterator和一個const iterator進行比較。如果d是deque(反過來就是const deque&),你不應該寫:
std::deque
while (i != d.end())
...
你應該寫:
std::deque
while (i != d.end())
...
C++標準沒有明確說第一種形式應該能工作,並且,的確,它不能在所有實作上都工作。如果避免它,你的程式將更可移植。當你正在定義你自己的配對的iterator和const iterator類時,你能容易地確保兩者間的比較將正確工作;只要確保定義比較操作為非模板的友元函式。
Listing 1: An Slist_iterator class, complete except for the equality operator
template
struct choose;
template
struct choose
typedef IsTrue type;
};
template
struct choose
typedef IsFalse type;
};
template
struct slist_iterator {
typedef std::forward_iterator_tag iterator_category;
typedef T value_type;
typedef std::ptrdiff_t difference_type;
typedef typename choose
reference;
typedef typename choose
pointer;
typedef typename choose
slist_node
nodeptr;
slist_iterator(nodeptr x = 0) : p(x) { }
slist_iterator(const slist_iterator
: p(i.p) { }
reference operator*() const { return p->val; }
pointer operator->() const { return &(p->val); }
slist_iterator& operator++() {
p = p->next;
return *this;
}
slist_iterator operator++(int) {
slist_iterator tmp(*this);
++*this;
return tmp;
}
nodeptr p;
};
— End of Listing —
Listing 2: A complete implementation of Slist_iterator and a partial implementation of an Slist container
template
T val;
slist_node* next;
slist_node(const T& t, slist_node* p)
: val(t), next(p) { }
};
template
struct choose;
template
struct choose
typedef IsTrue type;
};
template
struct choose
typedef IsFalse type;
};
template
struct slist_iterator {
typedef std::forward_iterator_tag iterator_category;
typedef T value_type;
typedef std::ptrdiff_t difference_type;
typedef typename choose
reference;
typedef typename choose
pointer;
typedef typename choose
slist_node
nodeptr;
slist_iterator(nodeptr x = 0) : p(x) { }
slist_iterator(const slist_iterator
: p(i.p) { }
reference operator*() const { return p->val; }
pointer operator->() const { return &(p->val); }
slist_iterator& operator++() {
p = p->next;
return *this;
}
slist_iterator operator++(int) {
slist_iterator tmp(*this);
++*this;
return tmp;
}
friend bool operator==(const slist_iterator& x,
const slist_iterator& y) {
return x.p == y.p;
}
friend bool operator!=(const slist_iterator& x,
const slist_iterator& y) {
return x.p != y.p;
}
nodeptr p;
};
// This is not a complete container class. It is just
// enough to illustrate defining and using an iterator/
// const iterator pair.
template
slist_node
typedef slist_iterator
typedef slist_iterator
iterator begin() { return iterator(head); }
iterator end() { return iterator(0); }
const_iterator begin() const {
return const_iterator(head);
}
const_iterator end() const {
return const_iterator(0);
}
slist() : head(0) { }
~slist() {
while (head) {
slist_node
delete head;
head = next;
}
}
void push_front(const T& t) {
head = new slist_node
}
...
};
— End of Listing —
Notes and References
[1] Matt Austern. Generic Programming and the STL (Addison-Wesley, 1998).
[2] Using partial specialization to define a compile-time ?: operator (as well as other compile-time Boolean operations) is a relatively old a; using it as a clean solution to the iterator/const iterator problem is more recent. I learned of the latter idea from Jeremy Siek.
[3] This is an open issue under consideration by the C++ standardization committee. See .
[4] The full rules for template argument deduction are described in §14.8.2.1 of the C++ Standard.
[5] See §14.5.3 of the C++ Standard.
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993496/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- CUJ:高效使用標準庫:set的iterator是mutable的還是immutable的? (轉)
- c/c++ 標準庫 迭代器(iterator)C++
- CUJ:標準庫:bitset和bit vector (轉)
- CUJ:標準庫:標準庫中的搜尋演算法 (轉)演算法
- CUJ:標準庫:容納指標的容器 (轉)指標
- CUJ:標準庫:基於檔案的容器 (轉)
- Iterator和ListIterator
- CUJ:高效使用標準庫:STL中的unary predicate (轉)
- Iterator 和 for…of 迴圈
- s:iterator標籤的使用
- webwork iterator標籤的用法Web
- CUJ:標準庫:容納不完全型別的容器 (轉)型別
- vector和iterator及collection
- Iterator原理
- Iterator模式模式
- iterator標籤總結(不斷更新)
- ECMAScript Iterator和for...of迴圈
- Java中Collection和Iterator介面Java
- boost::iterator_adaptor (I) (轉)APT
- boost::iterator_adaptor (II) (轉)APT
- boost::iterator_adaptor (III) (轉)APT
- Symbol.iteratorSymbol
- Iterator & foreach
- Iterator迭代器
- java容器-IteratorJava
- java使用iteratorJava
- 迭代器 iterator
- iterator移動
- 淺談如何實現自定義的 iterator
- 關於Struts的logic:iterator標籤
- python generator iterator和iterable objectPythonObject
- 迭代器模式(Iterator)模式
- Iterator及Enumeration
- Android Iterator 使用Android
- STL iterator delete problemdelete
- 基於 Generator 和 Iterator 的惰性列表
- Iterator和ListIterator有什麼區別
- 淺談如何實現自定義的 iterator 之二