頭文件 <boost/operators.hpp> 提供了幾組類模板(在名字空間 boost 中
)。這些模板在名字空間範圍中依據某個類提供的最小數量的基本操作符定義其它操作符。
類類型的重載操作符通常都成組出現。如果你可以寫 x + y
, 你可能也想要可以寫 x += y
. 如果你可以寫 x < y,
你也會想要 x > y, x >= y,
和 x <= y
.
而且,除非你的類真的有些令人驚訝的行為,否則這些相關的操作符中的一些應該可以由其它操作符定義(如 x >= y
<=> !(x < y)
)。在多個類中重複這些定義是乏味且易出錯的。boost/operators.hpp 中的模板幫助你在名字空間範圍內基於已經在你的類中定義了的操作符,生成其它操作符。
例如,如果你聲明了這樣的一個類:
class MyInt
: boost::operators<MyInt>
{
bool operator<(const MyInt& x) const;
bool operator==(const MyInt& x) const;
MyInt& operator+=(const MyInt& x);
MyInt& operator-=(const MyInt& x);
MyInt& operator*=(const MyInt& x);
MyInt& operator/=(const MyInt& x);
MyInt& operator%=(const MyInt& x);
MyInt& operator|=(const MyInt& x);
MyInt& operator&=(const MyInt& x);
MyInt& operator^=(const MyInt& x);
MyInt& operator++();
MyInt& operator--();
};
那麼 operators<>
模板添加了超過一打的操作符,如
operator>
, <=
, >=
, 和
(二元) +
. 此外還提供了模板的 兩參數形式,可以與其它類型進行互操作。
operator+=(T
const&)
進而提供 operator+(T const&, T
const&)
.本文所討論的概念並不像標準庫中的概念(可複製構造,等等)那樣是必需的,雖然它們中有一些是;我們稱它們為 弱概念(concepts with a small 'c')。具體地說,它們與前者的差別在於它們不描述操作符所要求定義的精確語義,除了以下要求:(a) 屬於同一概念的多個操作符的語義必須一致(如,a += b
應該與
a = a + b
表達式有相同操作),以及 (b) 操作符的返回類型應該跟隨內建類型的相應操作符的返回類型(如,operator<
應返回一個可轉換為 bool
的類型,而 T::operator-=
應返回可轉換為 T 的類型
)。這樣的 "寬鬆" 要求使得 operators
庫可應用於不同領域的、更廣泛的目標類,因而也更加有用。
二元操作符的兩個參數通常都是相同類型的,但通常都不想定義多個操作符來組合不同的類型。例如,你可能想將一個數學向量乘以一個比例係數。算術操作符模板的兩參數模板形式就是為此目的而提出的。提供一個模板的兩參數形式時,期望的操作符返回類型通常決定了我們討論的兩個類型中的哪一個應派生自操作符模板。例如,如果 T + U
的結果為類型 T
, 則
T
(而不是 U
) 應派生自 addable<T, U>
. 但比較運算的模板
(less_than_comparable<T,
U>
, equality_comparable<T, U>
,
equivalent<T, U>
, 和
partially_ordered<T,
U>
) 除外,因為它們所定義的操作符的返回類型為 bool
.
在不支持偏特化的編譯器上,兩參數形式必須使用結尾為'2'的名字來指定。而單參數形式也因為對稱性而提供了結尾為'1'的名字,以便可用於使用 基類鏈 技術的應用。
兩參數模板形式的另一個應用是類型 T 和另一個可轉換為 T 的類型 U 之間混合運算。這時兩參數模板形式在兩個方面有所幫助:一個是為操作符重載提供各自的符號特徵,另一個是性能。
在操作符重載方面,假定 U 為 int
, 而 T
是一個用戶自定義的無限整數類型,且已經有一個 double
operator-(double, const T&)
. 如果你想計算 int - T
而沒有提供 T operator-(int, const
T&)
, 那麼編譯器就是認為 double operator-(double,
const T&)
要比 T operator-(const
T&, const T&)
更為適合,但這很可能不是用戶的意圖。為了定義一套完整的操作符符號特徵,我們另外提供了兩參數模板形式的 '左' 形式
(subtractable2_left<T,
U>
, dividable2_left<T,
U>
, modable2_left<T,
U>
),以定義 U 出現在左邊時的不可交換操作符的符號特徵
(operator-(const U&, const T&)
,
operator/(const U&, const T&)
, operator%(const
U&, const T&)
).
在性能方面,我們注意到如果對混合類型運算使用單類型的二元操作符,那麼類型 U
的參數就必須被轉換為類型 T。但是實際上,通常有更高效的實現,即使用 T::operator-=(const U&)
以避免從 U 到 T 的轉換。算術操作符的兩參數模板形式創建了額外的操作符接口以使用更為高效的實現。不過對於 '左'
形式就不能獲得更高的效率:它們仍然需要從 U 到 T 的轉換,如果單類型二元操作符是最好的選擇時,我們的實現就相當於編譯器自動生成的代碼。
除了 arithmetic examples 和 iterator
helpers, 每個操作符類模板都有一個額外但可選的模板類型參數
B
. 這個參數是實例化模板的一個公有派生基類。這意味著它必須是一個類類型。它可以用於避免對像大小的膨脹,該問題在從多個空基類進行多重繼承時會經常遇到(詳情請見 舊版本用戶的注意事項)。為提供對一組操作符的支持,可以使用 B
參數來將操作符模板串成一個單基類的類層次,示例請見 用例。這個技術也被用於將多個操作符模板組合為復合操作符的定義。如果鏈過長超出了編譯器的支持範圍,請嘗試將某些操作符模板替換為單個復合操作符模板;長度的限制僅適用於鏈中的直接模板數量,而隱藏於複合模板中的模板則不算在內。
警告: 在使用一個 Boost 操作符模板的 單參數形式 時,如果要鏈一個不是 Boost 操作符模板的基類,你必須用結尾為'1'的名字來指定這個操作符模板。否則,本庫會假定你想定義一個二元操作來組合那個你想用作基類的類和你正在派生的類。
在某些編譯器上(如 Borland, GCC),單繼承也會在某些情況下引起對像大小的增加。如果你不是在定義一個類模板,你可以通過避免派生來獲得更小的對象大小,而是象下面這樣使用顯式的操作符模板實例化:
class myclass // 沒有繼承...
{
//...
};
// 顯式實例化我需要的操作符
template struct less_than_comparable<myclass>;
template struct equality_comparable<myclass>;
template struct incrementable<myclass>;
template struct decrementable<myclass>;
template struct addable<myclass,long>;
template struct subtractable<myclass,long>;
注意,有些操作符模板不能用這個方法,而必須作為它們的主操作數類型的基類。這些模板定義的操作符必須是成員函數,而上述方法要求操作符是獨立的友元函數。這類模板有:
dereferenceable<>
indexable<>
正如 Daniel Krügler 提出的,這個技巧違犯了 14.6.5/2,是不可移植的。原因是,實例化所帶入的操作符,如
less_than_comparable<myclass>
根據 3.4.2/2 中的規則不能被 ADL 查找到,由於 myclass 不是
less_than_comparable<myclass>
的關聯類型。因此,應該只在其它方法都失敗時才使用這個技巧。
很多編譯器(如 MSVC 6.3, GCC 2.95.2)並不強制執行在操作符模板表格中的要求,除非是真正使用時要依賴的操作。這不是符合標準的行為。具體地說,雖然從 operators<>
和 operators2<>
模板派生你的所有需要二元操作符的類,而不管它們是否實現了這些模板的所有要求的這種方法是很方便,但是這種取巧的方法是不可移植的。即使它們現在可以在你的編譯器上使用,但可能以後就不行了。
這個例子示範了某些 算術操作符模板 如何被用於一個表示幾何點的類(模板)。
template <class T>
class point // 註:這裡使用私有繼承是 OK 的!
: boost::addable< point<T> // point + point
, boost::subtractable< point<T> // point - point
, boost::dividable2< point<T>, T // point / T
, boost::multipliable2< point<T>, T // point * T, T * point
> > > >
{
public:
point(T, T);
T x() const;
T y() const;
point operator+=(const point&);
// point operator+(point, const point&) 被 addalbe 自動生成
point operator-=(const point&);
// point operator-(point, const point&) 被 subtractable 自動生成
point operator*=(T);
// point operator*(point, const T&) 和
// point operator*(const T&, point) 被 multipliable 自動生成
point operator/=(T);
// point operator/(point, const T&) 被 dividable 自動生成
private:
T x_;
T y_;
};
// 現在使用 point<> 類:
template <class T>
T length(const point<T> p)
{
return sqrt(p.x()*p.x() + p.y()*p.y());
}
const point<float> right(0, 1);
const point<float> up(1, 0);
const point<float> pi_over_4 = up + right;
const point<float> pi_over_4_normalized = pi_over_4 / length(pi_over_4);
算術操作符模板使得創建定制化的數字類型更為容易。給定一組核心的操作符,這些模板就會為該數字類型增加其它相關的操作符。這些操作符與標準算術類 型所有的操作符相類似,包括有比較、相加、遞增、邏輯和位操作等。更進一步,由於多數數字類型需要多個操作符,所以還提供了一些模板來將多個基本操作符模 板組合成一個聲明。
對於簡單操作符模板實例化所用的類型應滿足的要求,是按照有效表達式和表達式返回類型來指定的。而對於復合操作符模板,則只列出它們使用的模板。復合操作符模板所提供的操作和要求可以由所列出的組件的操作和要求來推斷。
我們說這些模板是"簡單"的,因為它們提供的操作符是基於類型必須提供的某個單一操作的。它們都有一個額外的可選模板參數 B
以使用
基類鏈 技巧,但下面不列出該參數。
|
||||||
模板 | 提供的操作 | 要求 | ||||
---|---|---|---|---|---|---|
less_than_comparable<T> less_than_comparable1<T> |
bool operator>(const T&, const T&) bool operator<=(const T&, const T&) bool operator>=(const T&, const T&) |
t < t1 .返回類型可轉換為 bool . 請見 Ordering Note. |
||||
less_than_comparable<T,
U> less_than_comparable2<T, U> |
bool operator<=(const T&, const U&) bool operator>=(const T&, const U&) bool operator>(const U&, const T&) bool operator<(const U&, const T&) bool operator<=(const U&, const T&) bool operator>=(const U&, const T&) |
t < u . t > u .返回類型可轉換為 bool . 請見 Ordering Note. |
||||
equality_comparable<T> equality_comparable1<T> |
bool operator!=(const T&, const T&) |
t == t1 .返回類型可轉換為 bool . |
||||
equality_comparable<T,
U> equality_comparable2<T, U> |
bool operator==(const U&, const T&) bool operator!=(const U&, const T&) bool operator!=(const T&, const U&) |
t == u .返回類型可轉換為 bool . |
||||
addable<T> addable1<T> |
T operator+(const T&, const T&) |
T temp(t); temp += t1 .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
addable<T, U> addable2<T, U> |
T operator+(const T&, const U&) T operator+(const U&, const T& ) |
T temp(t); temp += u .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
subtractable<T> subtractable1<T> |
T operator-(const T&, const T&) |
T temp(t); temp -= t1 .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
subtractable<T,
U> subtractable2<T, U> |
T operator-(const T&, const U&) |
T temp(t); temp -= u .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
subtractable2_left<T,
U> |
T operator-(const U&, const T&) |
T temp(u); temp -= t .返回類型可轉換為 T . |
||||
multipliable<T> multipliable1<T> |
T operator*(const T&, const T&) |
T temp(t); temp *= t1 .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
multipliable<T,
U> multipliable2<T, U> |
T operator*(const T&, const U&) T operator*(const U&, const T&) |
T temp(t); temp *= u .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
dividable<T> dividable1<T> |
T operator/(const T&, const T&) |
T temp(t); temp /= t1 .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
dividable<T, U> dividable2<T, U> |
T operator/(const T&, const U&) |
T temp(t); temp /= u .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
dividable2_left<T,
U> |
T operator/(const U&, const T&) |
T temp(u); temp /= t .返回類型可轉換為 T . |
||||
modable<T> modable1<T> |
T operator%(const T&, const T&) |
T temp(t); temp %= t1 .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
modable<T, U> modable2<T, U> |
T operator%(const T&, const U&) |
T temp(t); temp %= u .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
modable2_left<T,
U> |
T operator%(const U&, const T&) |
T temp(u); temp %= t .返回類型可轉換為 T . |
||||
orable<T> orable1<T> |
T operator|(const T&, const T&) |
T temp(t); temp |= t1 .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
orable<T, U> orable2<T, U> |
T operator|(const T&, const U&) T operator|(const U&, const T&) |
T temp(t); temp |= u .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
andable<T> andable1<T> |
T operator&(const T&, const T&) |
T temp(t); temp &= t1 .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
andable<T, U> andable2<T, U> |
T operator&(const T&, const U&) T operator&(const U&, const T&) |
T temp(t); temp &= u .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
xorable<T> xorable1<T> |
T operator^(const T&, const T&) |
T temp(t); temp ^= t1 .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
xorable<T, U> xorable2<T, U> |
T operator^(const T&, const U&) T operator^(const U&, const T&) |
T temp(t); temp ^= u .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
incrementable<T> |
T operator++(T&, int) |
T temp(t); ++t 返回類型可轉換為 T . |
||||
decrementable<T> |
T operator--(T&, int) |
T temp(t); --t; 返回類型可轉換為 T . |
||||
left_shiftable<T> left_shiftable1<T> |
T operator<<(const T&, const T&) |
T temp(t); temp <<= t1 .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
left_shiftable<T,
U> left_shiftable2<T, U> |
T operator<<(const T&, const U&) |
T temp(t); temp <<= u .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
right_shiftable<T> right_shiftable1<T> |
T operator>>(const T&, const T&) |
T temp(t); temp >>= t1 .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
right_shiftable<T,
U> right_shiftable2<T, U> |
T operator>>(const T&, const U&) |
T temp(t); temp >>= u .返回類型可轉換為 T . 請見 Symmetry Note. |
||||
equivalent<T> equivalent1<T> |
bool operator==(const T&, const T&) |
t < t1 .返回類型可轉換為 bool . 請見 Ordering Note. |
||||
equivalent<T, U> equivalent2<T, U> |
bool operator==(const T&, const U&) |
t < u . t > u .返回類型可轉換為 bool . 請見 Ordering Note. |
||||
partially_ordered<T> partially_ordered1<T> |
bool operator>(const T&, const T&) bool operator<=(const T&, const T&) bool operator>=(const T&, const T&) |
t < t1 . t == t1 .返回類型可轉換為 bool . 請見 Ordering Note. |
||||
partially_ordered<T,
U> partially_ordered2<T, U> |
bool operator<=(const T&, const U&) bool operator>=(const T&, const U&) bool operator>(const U&, const T&) bool operator<(const U&, const T&) bool operator<=(const U&, const T&) bool operator>=(const U&, const T&) |
t < u . t > u . t ==
u .返回類型可轉換為 bool . 請見 Ordering Note. |
less_than_comparable<T>
和
partially_ordered<T>
模板提供了兩組相同的操作。但是,less_than_comparable<T>
用於類型 T 的所有數值具有全序關係的情況。如果這不能滿足(如 IEEE 浮點算術中的 Not-a-Number 值),那就應該用 partially_ordered<T>
. partially_ordered<T>
模板可以用於全序的類型,但是它不如
less_than_comparable<T>
高效。這一規則同樣適用於對 T 和 U 的所有數值進行排序的 less_than_comparable<T, U>
和
partially_ordered<T,
U>
,以及兩個版本的 equivalent<>
. 對於 equivalent<>
,解決方案是為目標類寫一個定制的
operator==
.
在討論對稱性之前,我們需要討論一下優化的問題,搞清楚不同風格的操作符實現的原因。我們以一個類 T 的 operator+
為例:
T operator+( const T& lhs, const T& rhs )這是
{
return T( lhs ) += rhs;
}
operator+
的一個普通實現,但不是最高效的。其中創建了一個 lhs
的匿名局部拷貝,並對其調用 operator+=
再將它複製到函數的返回值(類型 T 的另一個匿名對像
)。標準通常不允許將臨時對像優化掉:
3.7.2/2: 自動存儲的持續時間重要的是對 12.8 的引用:
如果一個命名自動對像有帶副作用的初始化或析構函數,那麼它不應在代碼塊結束之前銷毀,也不應被優化掉,即使它看起來是沒用的,除了在 12.8 中指定的類對像或其拷貝可以被優化掉。
12.8/15: 複製類對像這種優化就是著名的 named return value optimization (NRVO), 我們可以這樣來實現
...
對於一個返回類類型的函數,如果在返回語句中的表達式是一個局部對象的名字,且該局對象的cv-限定類型與函數的返回類型相同,實現中允許忽略掉這個持有函數返回值的臨時對象的創建,即使該類的複製構造函數或析構函數帶有副作用。
operator+
:
T operator+( const T& lhs, const T& rhs )在這個實現中,編譯器就可以去掉臨時對象了。不幸的是,不是所有編譯器都實現了 NRVO, 有些還以錯誤的方法實現 NRVO 使得在這裡不能使用。即使沒有 NRVO, 後一種寫法也不會比前一種更差,不過還有另一種可能的實現,它有一些非常特別的地方:
{
T nrv( lhs );
nrv += rhs;
return nrv;
}
T operator+( T lhs, const T& rhs )這裡與第一個實現的差別在於,
{
return lhs += rhs;
}
lhs
不再是接受一個常量引用再創建一個拷貝;取而代之的是
lhs
作為一個傳值的參數,因此它已經被拷貝了。這種方法允許在某些情況下執行另一種優化(12.2/2)。考慮 a + b + c,其中
a + b
的結果不會在作為 lhs 與 c 相加時進行一次拷貝。這比原來的代碼更高效,但不如使用 NRVO 的編譯器高效。對於多數人來說,更可能的是編譯器不支持 NRVO, 但是現在 operator+
的符號特徵有所變化。另外,對於
(a + b ) + c
和 a + ( b + c )
,所創建的對象數量也會不同。多數情況下,這不是什麼問題,但如果你的代碼依賴於函數的符號特徵,或者要求嚴格對稱的行為,你就應該在你的 user-config 中設置
BOOST_FORCE_SYMMETRIC_OPERATORS
. 這樣將強制使用上面第二種方法,即使編譯器沒有實現 NRVO. 以下模板提供了一些相關操作符的組合。例如,一個 addable 的類型通常也是 subractable 的,所以 additive
模板提供了這兩個操作符的組合。組合算術操作符模板有一個額外的模板參數 B
,下面沒有提到,用於 基類鏈 技巧。
|
|||
模板 | 操作符模板組件 | ||
---|---|---|---|
totally_ordered<T> totally_ordered1<T> |
|||
totally_ordered<T,
U> totally_ordered2<T, U> |
|||
additive<T> additive1<T> |
|||
additive<T, U> additive2<T, U> |
|||
multiplicative<T> multiplicative1<T> |
|||
multiplicative<T,
U> multiplicative2<T, U> |
|||
integer_multiplicative<T> integer_multiplicative1<T> |
|||
integer_multiplicative<T,
U> integer_multiplicative2<T, U> |
|||
arithmetic<T> arithmetic1<T> |
|||
arithmetic<T, U> arithmetic2<T, U> |
|||
integer_arithmetic<T> integer_arithmetic1<T> |
|||
integer_arithmetic<T,
U> integer_arithmetic2<T, U> |
|||
bitwise<T> bitwise1<T> |
|||
bitwise<T, U> bitwise2<T, U> |
|||
unit_steppable<T> |
|||
shiftable<T> shiftable1<T> |
|||
shiftable<T, U> shiftable2<T, U> |
|||
ring_operators<T> ring_operators1<T> |
|||
ring_operators<T,
U> ring_operators2<T, U> |
|||
ordered_ring_operators<T> ordered_ring_operators1<T> |
|||
ordered_ring_operators<T,
U> ordered_ring_operators2<T, U> |
|||
field_operators<T> field_operators1<T> |
|||
field_operators<T,
U> field_operators2<T, U> |
|||
ordered_field_operators<T> ordered_field_operators1<T> |
|||
ordered_field_operators<T,
U> ordered_field_operators2<T, U> |
|||
euclidian_ring_operators<T> euclidian_ring_operators1<T> |
|||
euclidian_ring_operators<T,
U> euclidian_ring_operators2<T, U> |
|||
ordered_euclidian_ring_operators<T> ordered_euclidian_ring_operators1<T> |
|||
ordered_euclidian_ring_operators<T,
U> ordered_euclidian_ring_operators2<T, U> |
算術操作符類模板 operators<>
和 operators2<>
是不可擴展的操作符組合類的例子。它們是從舊版本遺留下來的類模板,不能用於 基類鏈。
|
|||
模板 | 操作符模板組件 | ||
---|---|---|---|
operators<T> |
|||
operators<T, U> operators2<T, U> |
operators_test.cpp 程序示範了算術操作符模板的使用,可用於驗證正確的操作。有關各個平台上的測試結果,請查看 compiler status report.
迭代器輔助類 模板簡化了創建定制迭代器的工作。與算術類型相似,一個完整的迭代器也有許多操作符是 "冗余" 的,可以依據核心操作符來實現。
提領操作符 是由 迭代器輔助類 所引出的,不過通常用於非迭代器的上下文。許多冗余的迭代器操作符同時也是算術操作符,所以迭代器輔助類借用了許多前文定義的操作符。事實上,只需要定義兩個新的操作符(成員指針操作符 operator->
和下標操作符 operator[]
)!
對於可用於提領操作符實例化的類型的要求,被指定為一些有效表達式及其返回類型。對於復合操作符模板,只列出它的組件模板,以及可能的其它要求。
下表中的所有提領操作符接受一個可選的模板參數(未列出),用於 基類鏈。
|
||||||||
模板 | 提供的操作 | 要求 | ||||||
---|---|---|---|---|---|---|---|---|
dereferenceable<T,
P> |
P operator->() const |
(&*i) . 返回類型可轉換為 P . |
||||||
indexable<T, D,
R> |
R operator[](D n) const |
*(i + n) . 返回類型為
R . |
其有五個迭代器操作符類模板,每個對應不同的迭代器類別。下表列出了一個定制迭代器可定義的各個類別所對應的操作符組合。這些類模板有一個額外的可選模板參數 B
(未列出),以支持 基類鏈。
|
|||||||
模板 | 操作符模板組件 | ||||||
---|---|---|---|---|---|---|---|
input_iteratable<T,
P> |
|||||||
output_iteratable<T> |
|||||||
forward_iteratable<T,
P> |
|||||||
bidirectional_iteratable<T,
P> |
|||||||
random_access_iteratable<T, P, D,
R> |
同樣有五個迭代器輔助類模板,每一個對應不同的迭代器類別。這些類不能用於 基類鏈。以下概要列明瞭這些類模板同時支持來自於 迭代器操作符類模板 的迭代器操作符和C++標準所要求的迭代器 typedef
(iterator_category
, value_type
,
等等)。
|
|||||||
模板 | 操作及要求 | ||||||
---|---|---|---|---|---|---|---|
input_iterator_helper<T,
V, D, P, R> |
支持以下的操作並有相應要求 | ||||||
output_iterator_helper<T> |
支持以下的操作並有相應要求 參見 [1], [2]. | ||||||
forward_iterator_helper<T, V, D, P,
R> |
支持以下的操作並有相應要求 | ||||||
bidirectional_iterator_helper<T,
V, D, P, R> |
支持以下的操作並有相應要求 | ||||||
random_access_iterator_helper<T,
V, D, P, R> |
支持以下的操作並有相應要求
要滿足 隨機訪問迭代器,
還需要 x1 - x2 的返回類型可轉換為 D .
|
[1] 不像其它的迭代器輔助類模板,output_iterator_helper
只接受一個模板參數 -
其目標類的類型。雖然有些人認為這是不必要的限制,但是標準要求任何輸出迭代器的 difference_type
和 value_type
應該是 void
(24.3.1 [lib.iterator.traits]), 而
output_iterator_helper
模板遵從了這個要求。還有,標準中的輸出迭代器具有 void 指針和引用類型,所以
output_iterator_helper
也一樣。
[2] 因為 self-proxying 是實現輸出迭代器的最容易和最常見的方法(例如,標準庫中的插入迭代器 [24.4.2] 和流迭代器 [24.5]),
所以 output_iterator_helper
也支持這種慣用法,將 operator*
和 operator++
成員函數定義為直接返回迭代器本身的一個非常量引用。對 self-proxying 的支持允許我們在大多數情況可以把編寫一個輸出迭代器的工作量減少至只需要寫兩個成員函數 - 一個適當的構造函數和一個複製賦值操作符。例如,以下是 boost::function_output_iterator
迭代器的一個可能的實現:
template<class UnaryFunction>
struct function_output_iterator
: boost::output_iterator_helper< function_output_iterator<UnaryFunction> >
{
explicit function_output_iterator(UnaryFunction const& f = UnaryFunction())
: func(f) {}
template<typename T>
function_output_iterator& operator=(T const& value)
{
this->func(value);
return *this;
}
private:
UnaryFunction func;
};
注意,對 self-proxying 的支持並不妨礙你使用
output_iterator_helper
來實現其它不同種類的輸出迭代器。如果
output_iterator_helper
的目標類型提供了它自己的 operator*
或/與 operator++
定義,那麼這些操作符將被使用,而且
output_iterator_helper
所提供的相同操作符不會被實例化。
The iterators_test.cpp 程序示範了迭代器模板的使用,也可用於驗證正確的操作。以下是該測試程序中定義的定制迭代器。它示範了對必須定義的核心操作的正確(雖然簡單)實現,這樣迭代器輔助類才可以 "補充" 其它的迭代器操作。
template <class T, class R, class P>
struct test_iter
: public boost::random_access_iterator_helper<
test_iter<T,R,P>, T, std::ptrdiff_t, P, R>
{
typedef test_iter self;
typedef R Reference;
typedef std::ptrdiff_t Distance;
public:
explicit test_iter(T* i =0);
test_iter(const self& x);
self& operator=(const self& x);
Reference operator*() const;
self& operator++();
self& operator--();
self& operator+=(Distance n);
self& operator-=(Distance n);
bool operator==(const self& x) const;
bool operator<(const self& x) const;
friend Distance operator-(const self& x, const self& y);
};
有關各個平台上的測試結果,請查看 compiler status report.
本庫的接口及推薦用法的變更 來自於下面所討論的幾個實際問題。新版本的庫仍然保持與舊版本的後向兼容(因此你不一定要修改現有代碼),但是舊的用法不被推薦使用。雖然它比使用 基類鏈 更簡單也更直觀,但是我們發現從多個操作符模板進行派生的舊方法會導致結果類遠大於它們應有的大小。多數現代C++編譯器在從多個空基類進行派生時都會導致類的大小顯著膨脹,即使這些基類本身並沒有狀態。例如,這個 例子 中的
point<int>
在Win32平台的多個編譯器上的大小為 12-24 字節,而不是我們期望的 8 字節。
嚴格來說,這不是程序庫的錯誤--語言的規則允許編譯器在這種情況下對空基類進行優化。原則上任意數量的空基類可以被分配在同一個位置上,它們之中 沒有共同的祖先(請見標準的 section 10.5 [class.derived] paragraph 5)。但是語言定義也沒有要求實現一定要執行這個優化,而當前的編譯器只有少數在多重繼承中實現這種優化。更糟的是,目前也看不到現有的編譯器會有這方面的改進,因為這樣會破壞由同一個編譯器的兩個不同版本所生成的代碼之間的二進制兼容性。正如 Matt Austern 所說,"One of the few times when you have the freedom to do this sort of thing is when you're targeting a new architecture...". 另一方面,許多常見編譯器在單繼承層次中會使用空基類優化。
在討論了這個問題對於本庫用戶的重要性(本庫的目標是用於編寫輕量級的類,如
MyInt
或 point<>
),
以及上面所說的各種壓力之後,我們決定修改庫的接口以解決對像大小膨脹的問題,即使是在只支持最簡單方式的空基類優化的編譯器上也不會出現此類問題。當前
的庫接口就是這種改變的結果。雖然新的用法與舊的相比有點複雜,不過我們認為這是值得的,它使得該庫在真實世界中更為有用。Alexy
Gurtovoy 貢獻了支持新用法並保持後向兼容性的相關代碼。
Revised: 29 Oct 2004
Copyright © Beman Dawes, David Abrahams, 1999-2001.
Copyright © Daniel Frey, 2002-2004.
Use, modification, and distribution is subject to the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at www.boost.org/LICENSE_1_0.txt)