## 用法
為了開始使用Operators庫,為你的類實現適用的操作符,就要包含頭文件`"boost/operators.hpp"`, 并從一個或多個Operator基類(它們的名字與它們所表示的概念一樣)進行派生,它們都定義在名字空間 `boost`中。注意,繼承不一定要是公有的,私有繼承也可以。在這一節,我們將看到幾個使用不同概念的例子,并關注一下在C++里以及在概念上,算術操作符和關系操作符是如何工作的。作為第一個例子,我們定義一個類,`some_class`, 帶有一個 `operator<`. 我們決定把`operator<`所暗指的等價關系定義為 `operator==`. 這個工作可以通過從`boost::equivalent`繼承而完成。
```
#include <iostream>
#include "boost/operators.hpp"
class some_class : boost::equivalent<some_class> {
int value_;
public:
some_class(int value) : value_(value) {}
bool less_than(const some_class& other) const {
return value_<other.value_;
}
};
bool operator<(const some_class& lhs, const some_class& rhs) {
return lhs.less_than(rhs);
}
int main() {
some_class s1(12);
some_class s2(11);
if (s1==s2)
std::cout << "s1==s2\n";
else
std::cout << "s1!=s2\n";
}
```
`operator<` 依照成員函數 `less_than`實現。`equivalent` 基類的要求就是派生的類必須提供 `operator<` 。從`equivalent`派生時,我們要把派生類`some_class`作為模板參數傳送。在 `main` 里,使用了Operators庫為我們生成的`operator==` 。接下來,我們再看一來 `operator<` ,看看其它的關系操作符如何依照 less than 實現。
### 對比較操作符的支持
我們最常實現的關系操作符就是less than,也就是 `operator<`.?為了要把對象存入關聯容器,或者是為了要排序,我們都要提供它。然而,通常我們僅支持這一個操作符,這樣會把類的使用者弄糊涂。例如,多數人知道對`operator<`的結果取反就相當于 `operator>=`.\[1\] Less than 也可以用來計算 greater than, 等等。所以,一個支持less than關系的類的使用者有充足的理由相信,支持(至少隱式地支持)其它的比較操作符也應該是類的接口的一部分。唉,如果我們僅僅支持了 `operator<` 而忽略了其它的,這個類就不是它可以的或者它應該的那樣有用了。這里有一個類,它已經可以用于標準庫容器的排序程序。
> \[1\] 盡管也有很多人以為是 `operator>`!
```
class thing {
std::string name_;
public:
thing() {}
explicit thing(const std::string& name):name_(name) {}
friend bool operator<(const thing& lhs, const thing& rhs) {
return lhs.name_<rhs.name_;
}
};
```
這個類支持排序,也可以被存入關聯容器中,但它可能還不能滿足用戶的期望!例如,如果一個用戶需要知道 `thing a` 是否大于 `thing b`, 他就要這樣寫:
```
// is a greater than b?
if (b<a) {}
```
雖然這段代碼是正確的,但是它未能清晰地表達作者的意圖,而這對于代碼的正確性是很重要的。如果用戶想知道 `a` 是否小于或等于 `b`, 他不得不這樣寫:
```
// is a less than, or equal to, b?
if (!(b<a)) {}
```
同樣,這段代碼是正確的,但它會讓人糊涂;對于多數不留意的讀者來說,代碼的意圖真的很不清晰。如果要引入等價的概念,代碼將變得更令人糊涂,而我們是支持等價關系的(否則我們的類不能存入關聯容器中)。
```
// is a equivalent to b?
if (!(a<b) && !(b<a)) {}
```
請注意,等價和相等是不一樣的,后面的章節將展開討論這個主題。在C++中,前面所述的所有關系特性都有不同的表示方式,它們是通過不同的操作符來明確地進行測試的。前面的例子應該是象這樣(等價關系可能是個例外,但我們在這先不管它):
```
if (a>b) {}
if (a<=b) {}
if (a==b) {}
```
現在,注釋是多余的了,因為代碼已經講清楚了一切。照這個樣子,代碼是不能編譯的,因為 `thing` 類不支持 `operator>`, `operator<=`, 或 `operator==`. 但是,對于具有`less_than_comparable`概念的類型,這些操作符(除了 `operator==`)都能被表達,Operators庫可以幫助我們。我們要做的全部工作就是讓 `thing` 派生自 `boost::less_than_comparable`, 如下:
```
class thing : boost::less_than_comparable<thing> {
```
僅僅通過指定一個基類,就可以依照`operator<`實現所有的操作符,`thing` 類現在可以按你所期望的那樣工作了。如你所見,要從Operators庫中的類派生出 `thing` ,我們必須把 `thing` 作為模板參數傳遞給基類。這種技術將在后面的章節里討論。注意,`operator==` 對于支持 `less_than_comparable`的類并無定義,但我們還有一個概念可用,即 `equivalent`. 從 `boost::equivalent` 派生就可以增加 `operator==`, 但是要注意,這里的 `operator==` 是定義為等價關系,而不是相等關系。等價意味著嚴格的弱序關系\[2\]。我們最后版本的類 `thing` 看起來應該是這樣的:
> \[2\] 如果你對嚴格弱序感到奇怪,可以跳到下一節,但是不要忘了稍后回到這里!
```
class thing :
boost::less_than_comparable<thing>,
boost::equivalent<thing> {
std::string name_;
public:
thing() {}
explicit thing(const std::string& name):name_(name) {}
friend bool operator<(const thing& lhs,const thing& rhs) {
return lhs.name_<rhs.name_;
}
};
```
這個版本在`thing`的定義中僅給出了一個操作符,保持了定義的簡潔,依靠派生自 `less_than_comparable` 和 `equivalent`, 它提供了一整組有用的操作符。
```
bool operator<(const thing&,const thing&);
bool operator>(const thing&,const thing&);
bool operator<=(const thing&,const thing&);
bool operator>=(const thing&,const thing&);
bool operator==(const thing&,const thing&);
```
你肯定見過很多提供了多個操作符的類。這些類的定義很難閱讀,由于有太多的操作符函數的聲明/實現。通過從`operators`中的概念類派生,你提供了相同的接口,但做得更清楚也更少代碼。在類的定義中提及這些概念,可以使熟悉`less_than_comparable` 和 `equivalent`的讀者清楚地知道這個類支持這些關系操作符。
### Barton-Nackman技巧
在前面兩個例子中,我們看到從operator基類繼承的方法,一個看起來怪怪的地方是,把派生類傳給基類作為模板參數。這是一種著名的技巧,被稱為 Barton-Nackmann 技巧\[3\] 或 Curiously Recurring Template Pattern\[4\]。這種技巧所解決的問題是循環的依賴性。考慮一下實現一個泛型類,它為另一個定義了`operator<`的類提供`operator==` 。順便說一下,這就是這個庫(當然還有mathematics庫)中稱為 `equivalent` 的概念。很明顯,任何類要利用提供了這種服務的具體實現,它都要了解提供服務的這個類,我們以這個類所實現的概念來命名它,稱之為 `equivalent` 類。然而,我們剛剛還在說 `equivalent` 要了解那個它要為之定義`operator==`的類!這是一種循環的依賴性,乍一看,好象沒有辦法可以解決。但是,如果我們把 `equivalent` 實現為類模板,然后指定那個要定義`operator==`的類為模板的參數,這樣我們就已經有效地把相關類型,也即是那個派生類,加入到 `equivalent` 的作用域中了。以下例子示范了如何使用這個技巧。
> \[3\] 由John Barton 和 Lee Nackmann "發明"。
> \[4\] 由James Coplien"發明"。
```
#include <iostream>
template <typename Derived> class equivalent {
public:
friend bool operator==(const Derived& lhs,const Derived& rhs) {
return !(lhs<rhs) && !(rhs<lhs);
}
};
class some_class : equivalent<some_class> {
int value_;
public:
some_class(int value) : value_(value) {}
friend bool operator<(const some_class& lhs,
const some_class& rhs) {
return lhs.value_<rhs.value_;
}
};
int main() {
some_class s1(4);
some_class s2(4);
if (s1==s2)
std::cout << "s1==s2\n";
}
```
基類 `equivalent` 接受一個要為之定義`operator==`的類型為模板參數。它通過使用`operator<`為該參數化類型實現泛型風格的`operator==`。然后,類 `some_class` 想要利用 `equivalent` 的服務,就從它派生并把自己作為模板參數傳遞給 `equivalent`。因此,結果就是為類型`some_class`定義了 `operator==` ,是依照 `some_class`的 `operator<` 實現的。這就是Barton-Nackmann技巧的全部內容。這是一種簡單且非常有用的模式,相當優美。
### 嚴格弱序(Strick Weak Ordering)
在本書中,我已經兩次提到了嚴格弱序(strict weak orderings),如果你不熟悉它,本節將離開主題一會,給你解釋一下。嚴格弱序是兩個對象間的一種關系。首先我們來一點理論的講解,然后再具體地討論。一個函數 `f(a,b)` 如果實現了一種嚴格弱序關系,這里的 `a` 和 `b` 是同一類型的兩個對象,我們說, `a` 和 `b` 是等價的,如果`f(a,b)` 是 false 并且 `f(b,a)` 也是 false。這意味著 `a` 不在 `b`之前,而且 `b` 也不在 `a`之前。因此我們可以認為它們是等價的。此外,`f(a,a)` 必須總是 `false`\[5\],而且如果 `f(a,b)` 為 `true`, 則 `f(b,a)` 必須為 `false`.\[6\] 還有,如果 `f(a,b)` 與 `f(b,c)` 均為 `true`, 則有 `f(a,c)`.\[7\] 最后,如果 `f(a,b)` 為 `false` 且 `f(b,a)` 也為 `false`, 并且如果 `f(b,c)` 為 `false` 且 `f(c,b)` 也為 `false`, 則 `f(a,c)` 為 `false` 且 `f(c,a)` 為 `false`.\[8\]
> \[5\] 即自反性。
> \[6\] 即反稱性。
> \[7\] 即傳遞性。
> \[8\] 即等價關系的傳遞性。
我們前面的例子(類 `thing`)可以有助于澄清這個理論。`thing`的小于比較是依照`std::string`的小于比較實現的。也就是說,是一種字面的比較。因此,給出一個包含字符串"First"的 `thing a` ,和一個包含字符串"Second"的 `thing b` ,還有一個包含字符串"Third"的 `thing c` ,我們可以 `assert` 前面給出的定義和公理。
```
#include <cassert>
#include <string>
#include "boost/operators.hpp"
// Definition of class thing omitted
int main() {
thing a("First");
thing b("Second");
thing c("Third");
// assert that a<b<c
assert(a<b && a<c && !(b<a) && b<c && !(c<a) && !(c<b));
// 等價關系
thing x=a;
assert(!(x<a) && !(a<x));
// 自反性
assert(!(a<a));
// 反對稱性
assert((a<b)==!(b<a));
// 傳遞性
assert(a<b && b<c && a<c);
// 等價關系的傳遞性
thing y=x;
assert( (!(x<a) && !(a<x)) &&
(!(y<x) && !(x<y)) &&
(!(y<a) && !(a<y)));
}
```
現在,所有這些 `assert`s 都成立,因為 `std::string` 實現了嚴格弱序\[9\]。就象 `operator<` 可以定義一個嚴格弱序,`operator>`也可以。稍后,我們將看到一個非常具體的例子,看看如果我們未能區分等價(它是一個嚴格弱序所要求的)與相等(它不是嚴格弱序所要求的)之間的不同,將會發生什么。
> \[9\] 事實上,`std::string` 定義了一個全序,全序即是嚴格弱序外加一個要求:等價即為相等。
### 避免對象膨脹
在前面的例子中,我們的類派生自兩個基類:`less_than_comparable<thing>` 和 `equivalent<thing>`. 根據你所使用的編譯器,你需要為這個多重繼承付出一定的代價;`thing` 可能要比它所需的更大。標準允許編譯器使用空類優化來創建一個沒有數據成員、沒有虛擬函數、也沒有重復基類的基類,這樣在派生類的對象中只會占用零空間, 而多數現代的編譯器都會執行這種優化。不幸的是,使用Operators庫常常會導致從多個基類進行繼承,這種情況下只有很少編譯器會使用空類優化。為了 避免潛在的對象大小膨脹,Operators支持一種稱為基類鏈(base class chaining) 的技術。每個操作符類接受一個可選的額外的模板參數,該參數來自于它的派生類。采用以下方法:一個概念類派生自另一個,后者又派生自另一個,后者又派生自 另一個…(你應該明白了吧),這樣就不再需要多重繼承了。這種方法很容易用。不要再從幾個基類進行繼承了,只要簡單地把幾個類鏈在一起就行 了,如下所示:
```
// Before
boost::less_than_comparable<thing>,boost::equivalent<thing>
// After
boost::less_than_comparable<thing,boost::equivalent<thing> >
```
這種方法避免了從多個空基類進行繼承,而從多個基類繼承可能會阻礙你的編譯器進行空類優化,使用從一 個空基類鏈進行繼承的方法,增加了編譯器進行空類優化的機會,也減少了派生類的大小。你可以用你的編譯器做一下試驗,看看你可以從這個技術中獲得多少好 處。注意,基類鏈長度是有限制的,這取決于編譯器。對于程序員可以接受的基類鏈長度也是很有限的!這意味著對那些需要從很多operator類進行繼承的類來說,我們需要把它們組合起來。更好的方法是,使用Operators庫已提供的復合概念。
以我的測試來看,在某個對多重繼承不執行空類優化的流行編譯器上\[10\], 使用基類鏈和使用多重繼承所得到的類的大小有很大的差別。使用基類鏈確實可以避免類型增大的負作用,而使用多重繼承則會有類型的增大,對于一個普通類型, 大小將增加8個字節(無可否認,8個額外的字節對于多數應用來說并不是問題)。如果被包裝的類型的大小非常小,那么多重繼承帶來的額外開銷就不是可以接受 的了。由于基類鏈很容易使用,我們應該在所有情況下都使用它!
> \[10\] 我這樣說一方面是因為沒有必要講出它的名字,另一方面也因為每個人都知道我說的是 Microsoft的老編譯器 (他們的新編譯器可能不是)。
### Operators 與不同的類型
有時候,一個操作符要包括一個以上的類型。例如,考慮一個字符串類,它支持通過`operator+` 和 `operator+=` 從字符數組進行字符串連接。這種情況下,Operators庫也可以幫忙,使用雙參數版本的操作符模板。這個字符串類可能擁有一個接受`char*`的轉換構造函數,但正如我們將看到的,這不能解決這個類的所有問題。以下是我們要用的字符串類。
```
class simple_string {
public:
simple_string();
explicit simple_string(const char* s);
simple_string(const simple_string& s);
~simple_string();
simple_string& operator=(const simple_string& s);
simple_string& operator+=(const simple_string& s);
simple_string& operator+=(const char* s);
friend std::ostream&
operator<<(std::ostream& os,const simple_string& s);
};
```
如你所見,我們為`simple_string`增加兩個版本的 `operator+=` 。一個接受 `const simple_string&`, 另一個接受 `const char*`. 這樣,我們的類支持如下用法:
```
simple_string s1("Hello there");
simple_string s2(", do you like the concatenation support?");
s1+=s2;
s1+=" This works, too";
```
雖然前面的工作符合要求,但我們還沒有提供二元的`operator+`,這個疏忽肯定是類的使用者所不樂意的。注意,對于我們的`simple_string`,我們可以通過忽略顯式的轉換構造函數來允許字符串連接。但是,這樣做會導致對字符緩沖的一次額外(不必要)的復制,而僅僅節省了一個操作符的定義。
```
// 以下不能編譯
simple_string s3=s1+s2;
simple_string s4=s3+" Why does this class behave so strangely?";
```
現在讓我們來用Operators庫來為這個類提供漏掉的操作符。注意共有三個操作符沒有提供。
```
simple_string operator+(const simple_string&,const simple_string&);
simple_string operator+(const simple_string& lhs, const char* rhs);
simple_string operator+(const char* lhs, const simple_string& rhs);
```
如果手工定義這些操作符,很容易就會忘記那個接受一個 `const simple_string&` 和一個 `const char*`的重載。而使用Operators庫,你就不會忘記了,因為庫已經為你實現了這些漏掉的操作符!我們想為 `simple_string` 做的就是加一個 addable 概念,所以我們只要簡單地從`boost::addable<simple_string>`派生 `simple_string` 就行了。
```
class simple_string : boost::addable<simple_string> {
```
但是,在這個例子中,我們還想要一個允許`simple_string` 和 `const char*`混合使用的操作符。為此,我們需要指定兩個類型,結果類型是`simple_string`, 以及第二參數類型是 `const char*`. 我們可以利用基類鏈來避免增大類的大小。
```
class simple_string :
boost::addable<simple_string,
boost::addable2<simple_string,const char*> > {
```
這就是為了支持我們想要的全部操作符所要做的全部事情!如你所見,我們用了一個不同的operator類:`addable2`. 如果你用的編譯器支持模板偏特化,你就不需要限定這個名字;你可以用 `addable` 代替 `addable2`. 為了對稱性,還有一個版本的類,它帶有后綴"1"。它可以增加可讀性,它總是明確給出參數的數量,它帶給我們以下對`simple_string`的派生寫法:
```
class simple_string :
boost::addable1<simple_string,
boost::addable2<simple_string,const char*> > {
```
選擇哪種寫法,完全取決于你的品味,如果你的編譯器支持模板偏特化,最簡單的選擇是忽略所有后綴。
```
class simple_string :
boost::addable<simple_string,
boost::addable<simple_string,const char*> > {
```
### 相等與等價的區別
為類定義關系操作符時,很重要的一點是分清楚相等和等價。要使用關聯容器,就要求有等價關系,它通過概念LessThanComparable\[11\]定義了一個嚴格弱序。這個關系只需最小的假設,并且對于要用于標準庫容器的類型來說,這是最低的要求。但是,相等與等價之間的區別有時會令人混淆,弄明白它們之間的差別是很重要的。如果一個類支持概念LessThanComparable, 通常它也就支持等價的概念。如果兩個元素進行比較,沒有一個比另一個小,我們稱它們是等價的。但是,等價并不意味著相等。例如,有可能在一個less than關系中忽略某些特性,但并不意味著它們就是相等的\[12\]。為了舉例說明這一點,我們來看一個類,`animal`, 它同時支持等價關系和相等關系。
> \[11\] 大寫的概念,如 LessThanComparable 直接來自于C++標準。所有 Boost.Operators 中的概念使用小寫名字。
> \[12\] 這意味著一個嚴格弱序,但不是一個全序。
```
class animal : boost::less_than_comparable<animal,
boost::equality_comparable<animal> > {
std::string name_;
int age_;
public:
animal(const std::string& name,int age)
:name_(name),age_(age) {}
void print() const {
std::cout << name_ << " with the age " << age_ << '\n';
}
friend bool operator<(const animal& lhs, const animal& rhs) {
return lhs.name_<rhs.name_;
}
friend bool operator==(const animal& lhs, const animal& rhs) {
return lhs.name_==rhs.name_ && lhs.age_==rhs.age_;
}
};
```
請注意 `operator<` 和 `operator==`的實現間的區別。在less than關系中僅使用了動物的名字,而在相等檢查中則同時比較了名字和年齡。這種方法沒有任何錯誤,但是它會導致有趣的結果。現在讓我們把這個類的一些元素存入 `std::set`. 和其它關聯容器一樣,`set` 僅依賴于概念 LessThanComparable. 以下面例子中,我們創建四個不一樣的動物,然后試圖把它們插入一個 `set`, 完全假裝我們不知道相等和等價之間的差別。
```
#include <iostream>
#include <string>
#include <set>
#include <algorithm>
#include "boost/operators.hpp"
#include "boost/bind.hpp"
int main() {
animal a1("Monkey", 3);
animal a2("Bear", 8);
animal a3("Turtle", 56);
animal a4("Monkey", 5);
std::set<animal> s;
s.insert(a1);
s.insert(a2);
s.insert(a3);
s.insert(a4);
std::cout << "Number of animals: " << s.size() << '\n';
std::for_each(s.begin(),s.end(),boost::bind(&animal::print,_1));
std::cout << '\n';
std::set<animal>::iterator it(s.find(animal("Monkey",200)));
if (it!=s.end()) {
std::cout << "Amazingly, there's a 200 year old monkey "
"in this set!\n";
it->print();
}
it=std::find(s.begin(),s.end(),animal("Monkey",200));
if (it==s.end()) {
std::cout << "Of course there's no 200 year old monkey "
"in this set!\n";
}
}
```
運行這個程序,會有以下完全荒謬的輸出結果。
```
Number of animals: 3
Bear with the age 8
Monkey with the age 3
Turtle with the age 56
Amazingly, there's a 200 year old monkey in this set!
Monkey with the age 3
Of course there's no 200 year old monkey in this set!
```
問題不在于猴子的年齡——它的確不尋常——而在于沒有了解這兩種關系概念間的區別。首先,當四個 `animal`s (`a1`, `a2`, `a3`, `a4`)被插入到 `set`, 第二只猴子,`a4`, 其實并沒有被插入,因為 `a1` 和 `a4` 是等價的。原因是,`std::set` 使用表達式 `!(a1<a4) && !(a4<a1)` 來決定是否已有一個匹配的元素。由于這個表達式的結果為 `true` (我們的 `operator<` 不比較年齡), 所以插入失敗了\[13\]。然后,當我們詢問這個set,使用`find`查找一個200歲的猴子時,它找到了這樣一只怪物。同樣,這是由于`animal`的等價關系,僅依賴于`animal`的`operator<` ,因而還是不關心年齡。最后,我們再次用 `find` 在 `set` 中定位這只猴子(`a1`), 但這次我們調用 `operator==` 來判斷它是否匹配,從而沒有找到匹配的猴子。通過對這些猴子的討論,不難理解相等與等價之間的差別,但你必須知道在給定的上下文中使用的是哪一個概念。
> \[13\] 一個set, 根據定義, 不存在重復的元素。
### 算術類型
定義算術類型時,Operators庫尤其有用。對于一個算術類型,通常有很多操作符要定義,而手工 去做這些工作是一項令人畏縮和沉悶的任務,并很可能發生錯誤或疏忽。Operators庫中定義的概念使這項工作變得容易,你只需為類定義最少的必須的操 作符,剩下的操作符就可以自動實現。考慮一個支持加法和減法的類。假設這個類使用一個內建類型作為實現。現在要增加適當的操作符,并確保它們不僅可以用于 這個類的實例,還可以用于可轉換為這個類的內建類型。你將要提供12個不同的加法和減法操作符。當然,更容易(也是更安全)的方法是,使用`addable` 和 `subtractable`類的雙參數形式。現在假設你還需要增加一組關系操作符。你可能要自己增加10個操作符,但現在你知道了最容易的方法是使用 `less_than_comparable` 和 `equality_comparable`. 這樣做之后,你擁有了22個操作符而只定義了6個。然而,你可能也注意到了這些概念對于數值類型來說是很常見的。的確如此,作為這四個類的替換,你可以僅使用 `additive` 和 `totally_ordered`.
我們先從四個概念類進行派生開始:`addable`, `subtractable`, `less_than_comparable`, 和 `equality_comparable`. 類`limited_type`, 僅僅包裝了一個內建類型并將所有操作符前轉給那個類型。它限制可用操作的數量,僅提供了關系操作符和加減法。
```
#include "boost/operators.hpp"
template <typename T> class limited_type :
boost::addable<limited_type<T>,
boost::addable<limited_type<T>,T,
boost::subtractable<limited_type<T>,
boost::subtractable<limited_type<T>,T,
boost::less_than_comparable<limited_type<T>,
boost::less_than_comparable<limited_type<T>,T,
boost::equality_comparable<limited_type<T>,
boost::equality_comparable<limited_type<T>,T >
> > > > > > > {
T t_;
public:
limited_type():t_() {}
limited_type(T t):t_(t) {}
T get() {
return t_;
}
// 為less_than_comparable提供
friend bool operator<(
const limited_type<T>& lhs,
const limited_type<T>& rhs) {
return lhs.t_<rhs.t_;
}
// 為equality_comparable提供
friend bool operator==(
const limited_type<T>& lhs,
const limited_type<T>& rhs) {
return lhs.t_==rhs.t_;
}
// 為addable提供
limited_type<T>& operator+=(const limited_type<T>& other) {
t_+=other.t_;
return *this;
}
// 為subtractable提供
limited_type<T>& operator-=(const limited_type<T>& other) {
t_-=other.t_;
return *this;
}
};
```
這是一個不錯的例子,示范了使用Operators庫后實現變得多么容易。僅需實現幾個必須實現的操 作符,就很容易地獲得了全組的操作符,而類也變得更易懂以及更易于維護。(即使實現這些操作符是困難的,你也可以把注意力集中于正確地實現它們)。這個類 唯一的潛在問題就是,它派生自八個不同的operator類,使用了基類鏈的方式,對于某些人而言,這可能不好閱讀。我們可以通過使用復合概念來大大簡化 我們的類。
```
template <typename T> class limited_type :
boost::additive<limited_type<T>,
boost::additive<limited_type<T>,T,
boost::totally_ordered<limited_type<T>,
boost::totally_ordered<limited_type<T>,T > > > > {
```
這更好看,而且也減少了擊鍵的次數。
### 僅在應用使用Operators時使用它
很明顯operators應該僅在適當的時候使用,但出于某些原因,operators的某些"很酷 的因素"常常誘使一些人在不清楚它們的語義時就使用它們。很多情形下都需要操作符,如在同一類型的實例間存在某種關系時,又或者在創建一個算術類型時。但 也有一些不太清晰的情形,你就需要考慮使用者的真正期望,如果用戶的期望是模糊的,最好還是選擇用成員函數。
多年以來,Operators已經被用于一些不平常的服務。增加操作符用于連接字符串,和使用位移操作符進行I/O,就是兩個操作符不再具有數學意義而被用于其它語義用途的最常見的例子。也有人對于在`std::map`中使用下標操作符訪問元素表示質疑(當 然其它人認為這是很自然的。他們是對的)。有時候,把操作符用于某種與內建類型規則不一致的用途是有意義的。而其它時候,它則是非常錯誤的,會引起混亂和 歧義。當你決定將一個操作符重載為與內建類型不一致的意義時,你必須很小心地去做。你必須確保它的意義是明顯的,并且優先級是正確的。這也是在 IOStream庫中使用位移操作符進行I/O的原因。位移操作符清晰地表明了將某物移向某個方向,并且位移操作符的優先級比多數操作符都低。如果你創建 一個表示汽車的類,可能發現 `operator-=` 很方便。但是,對于使用者這個操作符意味著什么?有些人可能認為它被用來表示在駕駛中使用的汽油。其它人可能認為它被用來表示汽車價值的貶值(當然會計師 會這樣想)。增加這個操作符是錯誤的,因為它沒有一個清晰的意圖,而一個成員函數可以更清楚地為這些操作命名。不要僅僅為了它可以寫出"酷"的代碼而增加 操作符。要因為它們真的有用而增加它們,確認增加所有適用的操作符,并且確認使用Boost.Operators庫!
### 弄明白它是如何工作的
我們現在來看一看這個庫是如何工作的,以進一步加深你對于如何正確使用它的理解。對于Boost.Operators, 這并不難。我們來看看如何實現對 `less_than_comparable` 的支持。你需要了解你要增加支持的那個類,并且你要為這個類增加操作符,這個操作符將用于實現該類的其它相關操作符。`less_than_comparable` 要求我們提供 `operator<`, `operator>`, `operator<=`, 和 `operator>=`. 現在,你已經知道如何依照`operator<`來實現 `operator>`, `operator<=`, and `operator>=` 。下面是一種可能的實現方法。
```
template <class T>
class less_than1
{
public:
friend bool operator>(const T& lhs,const T& rhs) {
return rhs<lhs;
}
friend bool operator<=(const T& lhs,const T& rhs) {
return !(rhs<lhs);
}
friend bool operator>=(const T& lhs,const T& rhs) {
return !(lhs<rhs);
}
};
```
對于 `operator>`, 你只需要交換兩個參數的順序。對于 `operator<=`, 注意到 `a<=b` 即意味著 `b` 不小于 `a`. 因此,實現的方法就是以相反的參數順序調用 `operator<` 并對結果取反。對于 `operator>=`, 同樣由于 `a>=b` 意味著 `a` 不小于 `b`. 因此,實現方法就是對調用 `operator<` 的結果取反。這是一個可以工作的例子:你可以直接使用它并且它將完成正確的工作。然而,如果可以提供一個支持`T`與兼容類型之間進行比較的版本就更好了,這只要簡單地增加幾個重載就可以了。出于對稱性的考慮,你應該允許兼容類型出現在操作符的左邊(這一點在手工增加操作符時很容易忘記;人們通常僅留意到操作符的右邊要接受其它類型。當然,你的雙類型版本 `less_than` 不會犯如此愚蠢的錯誤,對嗎?)
```
template <class T,class U>
class less_than2
{
public:
friend bool operator<=(const T& lhs,const U& rhs) {
return !(lhs>rhs);
}
friend bool operator>=(const T& lhs,const U& rhs) {
return !(lhs<rhs);
}
friend bool operator>(const U& lhs,const T& rhs) {
return rhs<lhs;
}
friend bool operator<(const U& lhs,const T& rhs) {
return rhs>lhs;
}
friend bool operator<=(const U& lhs,const T& rhs) {
return !(rhs<lhs);
}
friend bool operator>=(const U& lhs,const T& rhs) {
return !(rhs>lhs);
}
};
```
這就是了!兩個功能完整的 `less_than` 類。當然,要與Operators庫中的 `less_than_comparable` 具有同樣的功能,我們必須去掉表示使用幾個類型的后綴。我們真正想要的是一個版本,或者說至少是一個名字。如果你使用的編譯器支持模板偏特化,你就是幸運 的,因為基本上只要幾行代碼就可以實現了。但是,還有很多程序員沒有這么幸運,所以我們還是要用健壯的方法來實現它,以完全避開偏特化。首先,我們知道我 們需要某個東西用來調用 `less_than`, 它是一個接受一個或兩個類型參數的模板。我們也知道第二個類型是可選的,我們可以給它加一個缺省類型,我們知道用戶不會傳遞這樣一個類型給這個模板。
```
struct dummy {};
template <typename T,typename U=dummy> class less_than {};
```
我們需要某種機制來選擇正確版本的`less_than` (`less_than1` 或 `less_than2`);我們可以無需借助模板偏特化,而通過使用一個輔助類來做到,這個輔助類有一個類型參數,并內嵌一個接受另一個類的嵌套模板 `struct` 。然后,使用全特化,我們可以確保類型 `U` 是 `dummy`時,`less_than1` 將被選中。
```
template <typename T> struct selector {
template <typename U> struct type {
typedef less_than_2<U,T> value;
};
};
```
前面這個版本創建了一個名為 `value` 的類型定義,這個類型正是我們已經創建的? 模板的一個正確的實例化。
```
template<> struct selector<dummy> {
template <typename U> struct type {
typedef less_than1<U> value;
};
};
```
全特化的 `selector` 創建了另一個版本`less_than1`的 `typedef` 。為了讓編譯器更容易做,我們將創建另一個輔助類,專門負責收集正確的類型,并把它存入適當的typedef `type`.
```
template <typename T,typename U> struct select_implementation {
typedef typename selector<U>::template type<T>::value type;
};
```
這種語法看上去不討人喜歡,因為`selector`類中的嵌套模板 `struct`,但類的使用者并不需要看到這段代碼,所以這不是什么大問題。現在我們有了所有的因素,我們需要從中選擇一個正確的實現,我們最終從`select_implementation<T,U>::type`派生`less_than`,前者將會是 `less_than1` 或 `less_than2`, 這取決于用戶給出了一個還是兩個類型。
```
template <typename T,typename U=dummy> class less_than :
select_implementation<T,U>::type {};
```
就是它了!我們現在有了一個完全可用的 `less_than`,?由于我們付出的額外努力,增加了一種檢測并選擇正確的實現版本的機制,用戶現在可以以最容易的方式來使用它。我們還正確地了解了 `operator<` 如何用于創建一個`less_than_comparable`類所用的其它操作符。對其它操作符完成同樣的任務只需要小心行事,并弄清楚不同的操作符是如何共同組成新的概念的就行了。
### 剩下的事情
我們還沒有討論Operators庫中的剩余部分,迭代器助手類。我不想給出示例了,因為你主要是在 定義迭代器類型時會用到它們,這需要額外的解釋,這超出了本章甚至是本書的范圍。我在這里之所以提及它們,是因為如果你正在定義迭代器類型而不借助于 Boost.Iterators的話,你肯定會想用它些助手類的。解引用操作符幫助你定義正確的操作符而無須顧及你是否在需要一個代理類。在定義智能指針 是它們也很有用,智能指針通常要求定義 `operator->` 和 `operator*`. 迭代器助手類把不同類型的迭代器所需的概念組合在了一起。例如,一個隨機訪問迭代器必須是 `bidirectional_iterable`, `totally_ordered`, `additive`, 和 `indexable`的。定義新的迭代器類型時,更適當的做法是借助于Boost.Iterator庫,不過Operators庫也可以幫忙。
- 序
- 前言
- Acknowledgments
- 關于作者
- 本書的組織結構
- Boost的介紹
- 字符串及文本處理
- 數 據結構, 容器, 迭代器, 和算法
- 函數對象及高級編程
- 泛 型編程與模板元編程
- 數學及數字處理
- 輸入/輸出
- 雜項
- Part I: 通用庫
- Library 1. Smart_ptr
- Smart_ptr庫如何改進你的程序?
- 何時我們需要智能指針?
- Smart_ptr如何適應標準庫?
- scoped_ptr
- scoped_array
- shared_ptr
- shared_array
- intrusive_ptr
- weak_ptr
- Smart_ptr總結
- Library 2. Conversion
- Conversion 庫如何改進你的程序?
- polymorphic_cast
- polymorphic_downcast
- numeric_cast
- lexical_cast
- Conversion 總結
- Library 3. Utility
- Utility 庫如何改進你的程序?
- BOOST_STATIC_ASSERT
- checked_delete
- noncopyable
- addressof
- enable_if
- Utility 總結
- Library 4. Operators
- Operators庫如何改進你的程序?
- Operators
- 用法
- Operators 總結
- Library 5. Regex
- Regex庫如何改進你的程序?
- Regex 如何適用于標準庫?
- Regex
- 用法
- Regex 總結
- Part II: 容器及數據結構
- Library 6. Any
- Any 庫如何改進你的程序?
- Any 如何適用于標準庫?
- Any
- 用法
- Any 總結
- Library 7. Variant
- Variant 庫如何改進你的程序?
- Variant 如何適用于標準庫?
- Variant
- 用法
- Variant 總結
- Library 8. Tuple
- Tuple 庫如何改進你的程序?
- Tuple 庫如何適用于標準庫?
- Tuple
- 用法
- Tuple 總結
- Part III: 函數對象與高級編程
- Library 9. Bind
- Bind 庫如何改進你的程序?
- Bind 如何適用于標準庫?
- Bind
- 用法
- Bind 總結
- Library 10. Lambda
- Lambda 庫如何改進你的程序?
- Lambda 如何適用于標準庫?
- Lambda
- 用法
- Lambda 總結
- Library 11. Function
- Function 庫如何改進你的程序?
- Function 如何適用于標準庫?
- Function
- 用 法
- Function 總結
- Library 12. Signals
- Signals 庫如何改進你的程序?
- Signals 如何適用于標準庫?
- Signals
- 用法
- Signals 總結