## 用法
Any庫定義在名字空間 `boost` 內。你要用類 `any` 來保存值,用模板函數 `any_cast` 來取回存放的值。為了使用 `any`, 要包含頭文件 `"boost/any.hpp"`. 創建一個可以存放任意值的實例是很容易的。
```
boost::any a;
```
把任意類型的值賦給它也很容易。
```
a=std::string("A string");
a=42;
a=3.1415;
```
`any`幾乎可以接受任何東西!但是,為了真正能使用存放在`any`中的值,我們需要取回它,對吧?為此,我們需要知道這個值的類型。
```
std::string s=boost::any_cast<std::string>(a);
// 拋出 boost::bad_any_cast.
```
這顯然不行;因為當前的 `a` 所持的是一個 `double`, `any_cast` 拋出一個 `bad_any_cast` 異常。以下這樣則可以。
```
double d=boost::any_cast<double>(a);
```
`any` 只允許你在知道類型的前提下訪問它的值,這是很明智的。對于這個庫,典型情況下你只需記住兩件事:類 `any`, 用于存放值,還有模板函數 `any_cast`, 用于取回值。
### 任意的東西!
考慮三個類,`A`, `B`, 和 `C`, 它們沒有共同的基類,而我們想把它們存入一個 `std::vector`. 如果它們沒有共同基類,看起來我們不得不把它們當成 `void*` 來保存,對嗎?唔,not any more (相關語,沒有更多的了),因為類型 `any` 沒有改變對所存值的類型的依賴。以下代碼示范了如何解決這個問題。
```
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "boost/any.hpp"
class A {
public:
void some_function() { std::cout << "A::some_function()\n"; }
};
class B {
public:
void some_function() { std::cout << "B::some_function()\n"; }
};
class C {
public:
void some_function() { std::cout << "C::some_function()\n"; }
};
int main() {
std::cout << "Example of using any.\n\n";
std::vector<boost::any> store_anything;
store_anything.push_back(A());
store_anything.push_back(B());
store_anything.push_back(C());
// 我們再來,再加一些別的東西
store_anything.push_back(std::string("This is fantastic! "));
store_anything.push_back(3);
store_anything.push_back(std::make_pair(true, 7.92));
void print_any(boost::any& a);
// 稍后定義;打印a中的值
std::for_each(
store_anything.begin(),
store_anything.end(),
print_any);
}
```
運行以上例子,將有如下輸出。
```
Example of using any.
A::some_function()
B::some_function()
C::some_function()
string: This is fantastic!
Oops!
Oops!
```
好的,我們可以保存任意我們想要的東西,但我們如何取回保存在`vector`的元素中的值呢?在前例中,我們用 `for_each` 來為`vector`中的每個元素調用 `print_any()` 。
```
void print_any(boost::any& a) {
if (A* pA=boost::any_cast<A>(&a)) {
pA->some_function();
}
else if (B* pB=boost::any_cast<B>(&a)) {
pB->some_function();
}
else if (C* pC=boost::any_cast<C>(&a)) {
pC->some_function();
}
}
```
到目前為止,`print_any` 會試著取回一個指向 `A`, `B`, 或 `C` 對象的指針。這可以使用普通函數 `any_cast` 來完成,使用要"轉換"成的類型來特化該函數。看清楚些這個轉換,我們試著解開這個 `any a` ,對它說我們認為 `a` 包含一個類型 `A` 的值。請注意,我們以指針方式來傳遞我們的 `any` 給 `any_cast` 函數。因此,返回值將會是一個指向 `A`, `B`, 或 `C` 的指針。如果 `any` 沒有包含我們要轉換成的類型,將返回空指針。在本例中,如果轉型成功,我們就使用返回的指針來調用 `some_function` 成員函數。但 `any_cast` 也可以作一些小的調整。
```
else {
try {
std::cout << boost::any_cast<std::string>(a) << '\n';
}
catch(boost::bad_any_cast&) {
std::cout << "Oops!\n";
}
}
}
```
現在有點不同了。我們還是執行一個用我們要取回的類型來特化的 `any_cast` ,但不是用指針的方式來傳遞 `any` 實例,而是用 `const` 引用來傳遞。這改變了 `any_cast` 的行為;這種情況下,失敗——即請求一個錯誤的類型——將會拋出一個 `bad_any_cast` 類型的異常。因此,如果我們不能絕對肯定`any`中包含的是什么類型時,我們必須用一個`try`/`catch`塊來保護執行 `any_cast` 的代碼。這種行為上的差異(類似于 `dynamic_cast`)給了你更大的自由度。在轉型失敗不是一種錯誤時,使用指針來傳遞 `any`, 但如果轉型失敗是一種錯誤,則應該使用`const`引用來傳遞,這樣可以讓 `any_cast` 在失敗時拋出異常。
使用 `any` 讓你能夠在原來不能使用標準庫容器和算法的地方下使用它們,從而讓你可以寫出更具有可維護性和更易懂的代碼。
### 屬性類
現在我們想定義一個可用于容器的屬性類。我們將以字符串方式來保存屬性的名字,而屬性值則可以為任意 類型。雖然我們可以要求所有屬性值從一個共同的基類派生而來,但這樣常常不可行。例如,我們可能無法訪問所有那些我們需要用作屬性值的類的源代碼,也有可 能有些屬性值是內建類型,不可能是派生類(此外,那樣也不能做出一個好的 `any` 示例)。通過把屬性值存入一個 `any` 實例,我們可以讓使用者去處理那些他們知道類型且感興趣的屬性值。
```
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include "boost/any.hpp"
class property {
boost::any value_;
std::string name_;
public:
property(
const std::string& name,
const boost::any& value)
: name_(name),value_(value) {}
std::string name() const { return name_; }
boost::any& value() { return value_; }
friend bool operator<
(const property& lhs, const property& rhs) {
return lhs.name_<rhs.name_;
}
};
```
這個例子中的 `property` 類有一個保存為 `std::string` 的名字來標識,并用一個 `any` 來保存屬性值。`any` 為該實現帶來的靈活性在于,我們可以使用內建類型或用戶自定義類型而無須修改這個屬性類。不管它簡單或是復雜,一個 `any` 實例總是可以保存任意的東西。當然,使用 `any` 同時也意味著我們預先不知道可以對一個`property`中保存的屬性值進行哪些操作,我們需要首先把屬性值取出來。這暗示了如果對一個屬性類預先知道有哪些類型適用,我們可能要考慮`any`以外的其它方法。這在進行框架設計時是罕見的,如果我們不要求一個確定的基類,我們所能夠保證的就是,我們完全不知道會用到哪些類。如果你可以獲得任意類型而不需要對它做任何動作,除了保有它一段時間并稍后把它還回去,那么你會發現 `any` 是最合適的。注意,這個屬性類提供了 `operator<` 以允許該類可以保存在標準庫的關聯容器中;即使沒有這個操作符,`property` 仍然可以很好地用于序列容器。
以下程序使用了我們新的、靈活的`property`類,全虧了 `any`!`property` 類的實例被存入一個 `std::vector` 并以屬性名排序(譯注:原文是說存入一個 `std::map`,似有誤)。
```
void print_names(const property& p) {
std::cout << p.name() << "\n";
}
int main() {
std::cout << "Example of using any for storing properties.\n";
std::vector<property> properties;
properties.push_back(
property("B", 30));
properties.push_back(
property("A", std::string("Thirty something")));
properties.push_back(property("C", 3.1415));
std::sort(properties.begin(),properties.end());
std::for_each(
properties.begin(),
properties.end(),
print_names);
std::cout << "\n";
std::cout <<
boost::any_cast<std::string>(properties[0].value()) << "\n";
std::cout <<
boost::any_cast<int>(properties[1].value()) << "\n";
std::cout <<
boost::any_cast<double>(properties[2].value()) << "\n";
}
```
注意,我們不必為 `property` 的構造函數顯式創建 `any` 。這是因為 `any` 的類型轉換構造函數不是 explicit 的。雖然構造函數通常應該接受一個顯式聲明的參數,但 `any` 是這個規則的一個例外。運行這段程序會得到以下輸出。
```
Example of using any for storing properties.
A
B
C
Thirty something
30
3.1415
```
在這個例子中,由于容器被排序了,我們可以按索引取回屬性值,而且我們預先知道它們各自的類型,所以我們不需要用 `try`/`catch` 塊來處理取回操作。從一個 `any` 實例中取回值時,如果失敗表示一個真正的錯誤,則應該用 `const` 引用來傳遞 `any` 。
```
std::string s=boost::any_cast<std::string>(a);
```
當失敗不應是一個錯誤時,用指針來傳遞 `any` 。
```
std::string* ps=boost::any_cast<std::string>(&a);
```
這兩種不同風格的取回操作不僅在語義上有所不同,而且返回的值也不同。如果你傳遞一個指針參數,你會得到一個指向保存值的指針;如果你傳遞一個 `const` 引用參數,你會得到一個保存值的拷貝。
如果值的類型在拷貝時代價很昂貴,就應該傳遞指針以避免值的拷貝。
### 關于 any 的更多
`any` 還提供了其它幾個成員函數,如測試一個 `any` 實例是否為空,交換兩個 `any` 實例的值。以下例子示范了如何使用它們。
```
#include <iostream>
#include <string>
#include "boost/any.hpp"
int main() {
std::cout << "Example of using any member functions\n\n";
boost::any a1(100);
boost::any a2(std::string("200"));
boost::any a3;
std::cout << "a3 is ";
if (!a3.empty()) {
std::cout << "not ";
}
std::cout << "empty\n";
a1.swap(a2);
try {
std::string s=boost::any_cast<std::string>(a1);
std::cout << "a1 contains a string: " << s << "\n";
}
catch(boost::bad_any_cast& e) {
std::cout << "I guess a1 doesn't contain a string!\n";
}
if (int* p=boost::any_cast<int>(&a2)) {
std::cout << "a2 seems to have swapped contents with a1: "
<< *p << "\n";
}
else {
std::cout << "Nope, no int in a2\n";
}
if (typeid(int)==a2.type()) {
std::cout << "a2's type_info equals the type_info of int\n";
}
}
```
以下是這段程序的輸出結果。
```
Example of using any member functions
a3 is empty
a1 contains a string: 200
a2 seems to have swapped contents with a1: 100
a2's type_info equals the type_info of int
```
讓我們來更進一步分析這段代碼。為了測試一個 `any` 實例是否包含值,我們調用成員函數 `empty`. 我們這樣來測試 `any a3` 。
```
std::cout << "a3 is ";
if (!a3.empty()) {
std::cout << "not ";
}
std::cout << "empty\n";
```
因為我們是缺省構造 `a3` 的,因此 `a3.empty()` 返回 `true`. 下一件事情是交換 `a1` 和 `a2` 的內容。你可能會想為什么要交換它們的內容。一種可能的情形是當 `any` 實例的標識非常重要時(`swap` 僅交換其中包含的值)。另一個原因是在你不需要原來的值時避免拷貝。
```
a1.swap(a2);
```
最后,我們使用了成員函數 `type`, 它返回一個 `const std::type_ info&`, 用于測試所含的值是否類型 `int` 的值。
```
if (typeid(int)==a2.type()) {
```
注意,如果一個 `any` 保存了一個指針類型,則`std::type_info`表示的是相應的指針類型。
### 在any中保存指針
通常,測試 `empty` 足以知道對象是否真的包含有效的東西。但是,如果 `any` 持有的是一個指針,則要在解引用它之前額外小心地測試這個指針。僅僅簡單地測試 `any` 是否為空是不夠的,因為一個 `any` 在持有一個指針時會被認為是非空的,即使這個指針是空的。
```
boost::any a(static_cast<std::string*>(0));
if (!a.empty()) {
try {
std::string* p=boost::any_cast<std::string*>(a);
if (p) {
std::cout << *p;
}
else {
std::cout << "The any contained a null pointer!\n";
}
}
catch(boost::bad_any_cast&) {}
}
```
### 一個更好的辦法——使用shared_ptr
在 `any` 中保存裸指針的另一個麻煩在于析構的語義。`any` 類接受了它所存值的所有權,因為它保持一個該值的內部拷貝,并與 `any` 一起銷毀它。但是,銷毀一個裸指針并不會對它調用 `delete` 或 `delete[]` !它僅僅要求歸還屬于指針的那點內存。這使得在 `any` 中保存指針是有問題的,所以更好的辦法是使用智能指針來代替。的確,使用智能指針(見 "[Library 1](../Text/content.html#ch01): [Smart_ptr 1](../Text/content.html#ch01)")是在一個 `any` 中保存指針的好辦法。它解決了要保證所存指針指向的內存被正確釋放的問題。當智能指針被銷毀時,它會正確地確保內存及其中的數據被正確銷毀。作為對比,要注意 `std::auto_ptr` 不是合適的智能指針。這是因為 `auto_ptr` 沒有通常的復制語義;訪問一個 `any` 中的值會把內存及其中數據的所有權從 `any` 轉移到返回的 `auto_ptr` 中。
考慮以下代碼。
```
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include "boost/any.hpp"
#include "boost/shared_ptr.hpp"
```
首先,我們定義兩個類,`A` 和 `B`, 每個都有成員函數 `is_virtual`, 它是虛擬的,還有一個成員函數 `not_virtual`, 它不是虛擬的(如果它也是虛擬的,那么這個名字就真是糟透了)。我們想把這兩類對象存入 `any` 。
```
class A {
public:
virtual ~A() {
std::cout << "A::~A()\n";
}
void not_virtual() {
std::cout << "A::not_virtual()\n";
}
virtual void is_virtual () {
std::cout << "A:: is_virtual ()\n";
}
};
class B : public A {
public:
void not_virtual() {
std::cout << "B::not_virtual()\n";
}
virtual void is_virtual () {
std::cout << "B:: is_virtual ()\n";
}
};
```
我們現在來定義一個普通函數,`foo`, 它接受一個 `any` 引用的參數,并使用 `any_cast` 來嘗試將該 `any` 轉為這個函數知道如何處理的類型。如果不能匹配,這個函數簡單地忽略該 `any` 并返回。它分別對類型 `shared_ptr<A>` 和 `shared_ptr<B>` 進行測試,并對調用它們的 `is_virtual` (虛擬函數) 和 `not_virtual` 。
```
void foo(boost::any& a) {
std::cout << "\n";
// 試一下 boost::shared_ptr<A>
try {
boost::shared_ptr<A> ptr=
boost::any_cast<boost::shared_ptr<A> >(a);
std::cout << "This any contained a boost::shared_ptr<A>\n";
ptr-> is_virtual ();
ptr->not_virtual();
return;
}
catch(boost::bad_any_cast& e) {}
// 試一下 boost::shared_ptr<B>
try {
boost::shared_ptr<B> ptr=
boost::any_cast<boost::shared_ptr<B> >(a);
std::cout << "This any contained a boost::shared_ptr<B>\n";
ptr-> is_virtual ();
ptr->not_virtual();
return;
}
catch(boost::bad_any_cast& e) {}
// 如果是其它東西(如一個字符串), 則忽略它
std::cout <<
"The any didn't contain anything that \
concerns this function!\n";
}
```
在 `main` 里面,我們創建兩個 `any` 。然后我們引入一個作用域,再創建兩個新的 `any` 。接著,我們把所有 `any` 存入 `vector` 并把其中所有元素傳給函數 `foo`, 它測試它們的內容并操作它們。這時要注意我們違反了前面給出的建議,即在失敗不代表錯誤時應該使用指針形式的 `any_cast` 。但是,因為我們在這兒用的是智能指針,這時使用異常拋出形式的`any_cast`的語法優勢完全有理由忽略這個建議。
```
int main() {
std::cout << "Example of any and shared_ptr\n";
boost::any a1(boost::shared_ptr<A>(new A));
boost::any a2(std::string("Just a string"));
{
boost::any b1(boost::shared_ptr<A>(new B));
boost::any b2(boost::shared_ptr<B>(new B));
std::vector<boost::any> vec;
vec.push_back(a1);
vec.push_back(a2);
vec.push_back(b1);
vec.push_back(b2);
std::for_each(vec.begin(),vec.end(),foo);
std::cout << "\n";
}
1
std::cout <<
"any's b1 and b2 have been destroyed which means\n"
"that the shared_ptrs' reference counts became zero\n";
}
```
程序運行時,將有如下輸出。
```
Example of any and shared_ptr
This any contained a boost::shared_ptr<A>
A:: is_virtual ()
A::not_virtual()
The any didn't contain anything that concerns this function!
This any contained a boost::shared_ptr<A>
B:: is_virtual ()
A::not_virtual()
This any contained a boost::shared_ptr<B>
B:: is_virtual ()
B::not_virtual()
A::~A()
A::~A()
any's b1 and b2 have been destroyed which means
that the shared_ptrs' reference counts became zero
A::~A()
```
(譯注:最后三行輸出是原文沒有的,但應該有)
首先,我們看到傳給 `foo` 的 `any` 含有一個 `shared_ptr<A>`, 它恰好擁有一個 `A` 的實例。輸出正是我們所期望的。
接著是我們加到 `vector` 中的含有 `string` 的 `any` 。這顯示了保存一些對稍后要被調用的函數而言是未知類型的類型到一個 `any`,是很有可能的,通常也是合理的;這些函數只需要處理它們需要操作的類型!
接下來的事情更有趣了,第三個元素含有一個指向 `B` 實例的 `shared_ptr<A>` 。這是一個例子,說明了 `any` 如何與其它類型一樣實現多態性。當然,如果我們使用裸指針,就需要用 `static_cast` 來保存指針為我們想標識的類型。注意,函數 `A::not_virtual` 被調用而不是 `B::not_virtual`. 原原因是這個指針的靜態類型是 `A*`, 而不是 `B*`.
最后一個元素含有一個 `shared_ptr<B>` ,它也正好指向一個 `B` 的實例。再一次,我們可以控制保存在 `any` 的類型,在后面一個 try 中設置相應的參數來打開它。
在里面的那個作用域結束時,`vector` 被銷毀,它又銷毀了內含的 `any` 實例,后者再銷毀所有的 `shared_ptr`,正確地設置引用參數為零。接著,我們的指針被安全和不費力氣地銷毀!
這個例子顯示了一些比如何與 `any` 一起使用智能指針更為重要的東西;它(又一次)顯示了存入 `any` 的類型有是簡單的或是復雜的都無關緊要。如果復制被存值的代價是高得驚人的,或者如果需要共享使用和控制生存期,就應該考慮使用智能指針,就象使用標準庫的容器保存值一樣。同樣的推理也適用于 `any`, 通常這兩個原則是一致的,正如在容器中使用 `any` 就意味著要保存不同的類型。
### 輸入和輸出操作符怎么啦?
`any` 用戶的一個常見問題是,"為什么沒有輸入和輸出操作符?" 這真的是有原因的。讓我們從輸入操作符開始。輸入的語義應該是什么?它應該缺省為一個 `string` 類型嗎?`any`當前持有的類型應該被用于從流中讀取數據嗎?如果是的話,那么首先為什么要用 `any` 呢?這些問題沒有好的答案,這正是為什么 `any` 沒有輸入操作符的原因。回答第二個問題并不容易,但差不多。讓 `any` 支持一個輸出操作符意味著 `any` 不再能夠保存任意的類型,因為這個操作符對于保存在 `any` 中的類型增加了一個要求。如果我們不有意去使用`operator<<`,這本無關緊要;但一個含有不支持輸出操作符的類型的 `any` 實例仍是非法的,在編譯時會導致一個錯誤。當然,只要我們提供一個模板版本的 `operator<<`, 我們就可以使用 `any` 而無須要求被包含的類型支持流輸出,但一旦這個操作符被實例化,這個要求還是會被打開。
看起來,這些就是沒有這些操作符的原因了,對嗎?如果我們給可以匹配任意東西的 `any` 提供一個有效的輸出操作符,并把 `operator<<` 引入到只能從 `any` 類的實現細節進行訪問的作用域,它會是什么樣的呢?那樣的話,我們可以在執行輸出到一個流時選擇拋出一個異常或返回一個錯誤代碼(這個功能僅用于那些不支持 `operator<<` 的參數),我們將要在運行期去做這些動作,而不影響其它代碼的合法性。這種想法非常吸引我,我用幾個手邊的編譯器上試了一下。結果不太好。我不想詳細討論它,但簡而言之,這種方法需要一些很多編譯器目前還不能處理的技術。但是,我們無需修改 `any` 類,我們可以創建一個利用 `any` 來保存任意類型的新類,并讓這個新類支持 `operator<<`. 基本上,我們無論如何都需要做的是,`any` 要了解被含類型,知道如何進行輸出,然后加到輸出流中去。
### 增加對輸出的支持——any_out
我們將定義一個類,它具有通過 `operator<<` 進行輸出的功能。這增加了對被存類型的要求;作為可以保存在類 `any_out` 中的有效類型,必須要支持 `operator<<`.
```
#include <iostream>
#include <vector>
#include <string>
#include <ostream>
#include "boost/any.hpp"
class any_out {
```
該 `any_out` 類保存(任意的)值在一個 `boost::any` 類型中。總是應該選擇重用,而不是重新發明!
```
boost::any o_;
```
接下來,我們聲明一個抽象類 `streamer`, 它使用和 `any` 同樣的設計。我們不能直接使用泛型類型,因為那樣我們還不如泛化 `any_out` 算了,這樣會使 `any_out` 的類型依賴于它所含值的類型,在需要存儲不同類型的上下文中這樣的類是沒有用的。所含值的類型不能成為 `any_out` 類的標識的一部分。
```
struct streamer {
virtual void print(std::ostream& o,boost::any& a)=0;
virtual streamer* clone()=0;
virtual ~streamer() {}
};
```
這里有一個竅門:我們增加了一個泛型類 `streamer_imp`, 用所含類型來特化,并派生自 `streamer`. 因而,我們可以在 `any_out` 中保存一個 `streamer` 指針,并依靠多態性來完成剩余的工作(接下來,我們將為此增加一個虛擬成員函數)。
```
template <typename T> struct streamer_imp : public streamer {
```
現在,讓我們實現一個虛擬函數 `print` ,通過執行一個到用來特化 `streamer_imp` 的類型的 `any_cast` 來輸出 `any` 中所含的值。因為我們將會用與存入 `any` 中的值相同的類型來實例化一個 `streamer_imp`,因此這個轉型不會失敗。
```
virtual void print(std::ostream& o,boost::any& a) {
o << *boost::any_cast<T>(&a);
}
```
復制一個 `any_out` 時需要一個克隆函數,由于我們準備保存一個 `streamer` 指針,所以虛擬函數 `clone` 負責拷貝正確的 `streamer` 類型。
```
virtual streamer* clone() {
return new streamer_imp<T>();
}
};
class any_out {
streamer* streamer_;
boost::any o_;
public:
```
缺省構造函數用于創建一個空的 `any_out`, 并設置 `streamer` 指針為零。
```
any_out() : streamer_(0) {}
```
`any_out` 中最有趣的函數是泛型構造函數。通過存入的值來推斷出類型 `T`?,并用于創建 `streamer`. 同時,值被存入 `any o_`.
```
template <typename T> any_out(const T& value) :
streamer_(new streamer_imp<T>),o_(value) {}
```
復制構造函數很簡單;我們所需做的只是確保源 `any_out a` 中的 `streamer` 非零。
```
any_out(const any_out& a)
: streamer_(a.streamer_?a.streamer_->clone():0),o_(a.o_) {}<sup class="docfootnote">\[1\]</sup>
template<typename T> any_out& operator=(const T& r) {
any_out(r).swap(*this);
return *this;
}
any_out& operator=(const any_out& r) {
any_out(r).swap(*this);
return *this;
}
~any_out() {
delete streamer_;
}
```
> \[1\] Rob Stewart 問我寫這一行是否為了在讓人困惑的比賽中拿冠軍,或者只是想寫 ():0) 。我不能肯定,但可以肯定看到這一行你會開心….
`swap` 函數用于更容易地實現異常安全的賦值。
```
any_out& swap(any_out& r) {
std::swap(streamer_, r.streamer_);
std::swap(o_,r.o_);
return *this;
}
```
現在,我們來增加那個讓我們到此的東西:輸出操作符。它應該接受一個 `ostream` 引用和一個 `any_out` 引用。被保存在 `any_out` 中的 `any` 將被傳遞給 streamer 的虛擬函數 `print` 。
```
friend std::ostream& operator<<(std::ostream& o,any_out& a) {
if (a.streamer_) {
a.streamer_->print(o,a.o_);
}
return o;
}
};
```
這個類不僅提供了對包含在一個泛型類中的簡單(未知)類型執行流輸出的方法,它還示范了 `any` 是如何設計的。這種設計,以及這種用于安全地把一個類型包裝在一個多態化的表面之后的技術,是通用的,被應用于其它很多地方。例如,它可以用于創建一個泛型的函數適配器。
讓我們來測試一下我們的 `any_out` 類。
```
int main() {
std::vector<any_out> vec;
any_out a(std::string("I do have operator<<"));
vec.push_back(a);
vec.push_back(112);
vec.push_back(65.535);
// 打印vector vec中的所有東西
std::cout << vec[0] << "\n";
std::cout << vec[1] << "\n";
std::cout << vec[2] << "\n";
a=std::string("This is great!");
std::cout << a;
}
```
如果類 `X` 不支持 `operator<<`, 這段代碼就不能編譯。不幸的是,這與我們是否真的使用了 `operator<<` 無關,它就是不能工作。`any_out` 總是要求輸出操作符可用。
```
any_out nope(X());
std::cout << nope;
}
```
很方便,你不這樣認為嗎?如果在某個特定上下文中,你計劃使用的所有類型有某個共同的操作可用,你可以象我們前面為 `any_out` 類增加對 `operator<<` 的支持那樣加上它。推廣這種方法和泛化該操作并不難,這可以用來擴展 `any` 可重用性的接口。
### 謂詞
在我們結束關于 `any` 用法的這一節之前,讓我們看一下如何圍繞 `any` 來建立一些功能,來簡化使用和增加表現力。`any` 可用于在容器類中保存不同的類型,它可以很容易地保存這些值,但很難去操作它們。
首先,我們創建兩個謂詞,`is_int` 和 `is_string`, 它們分別用于判斷一個 `any` 是否包含一個 `int` 或一個 `string` 。在我們想在一個存有不同類型對象的容器中查找特定類型時,或者是想測試一個 `any` 的類型以決定后面的動作時,這很有用。實現方法是用 `any` 的成員函數 `type` 來測試。
```
bool is_int(const boost::any& a) {
return typeid(int)==a.type();
}
bool is_string(const boost::any& a) {
return typeid(std::string)==a.type();
}
```
這種辦法可以工作,但為每一種我們想測試的類型寫一個謂詞會很乏味。實現的方法是重復的,這很適合用模板的方法來解決,如下。
```
template <typename T> bool contains (const boost::any& a) {
return typeid(T)==a.type();
}
```
函數 `contains` 讓我們不必手工創建新的謂詞了。這是一個示范模板如何用來最小化冗余代碼的典型例子。
### 對非空值計數
對于某些應用,可能要對容器中所有元素進行迭代并測試每個 `any` 是否含有值。一個空的 `any` 可能意味著要被刪除,也可能我們要為了更一步的處理而取出所有非空的 `any` 元素。在一個算法中這是很常用到的,我們創建一個函數對象,它的函數調用操作符接受一個 `any` 參數。該操作符只是測試 `any` 是否為空,如果不是則遞增計數器。
```
class any_counter {
int count_;
public:
any_counter() : count_(0) {}
int operator()(const boost::any& a) {
return a.empty() ? count_ : ++count_;
}
int count() const { return count_; }
};
```
對于一個保存 `any` 的容器 `C` , 計算其中的非空值個數可以這樣寫。
```
int i=std::for_each(C.begin(),C.end(),any_counter()).count();
```
注意,`for_each` 算法返回的是函數對象,所以我們可以很容易地取到計數值。因為 `for_each` 是以值的方式接受參數的,所以以下代碼完成的不是同一件事情。
```
any_counter counter;
std::for_each(C.begin(),C.end(),counter);
int i=counter.count();
```
第二個版本總是得到 0, 因為函數對象 counter 在調用 `for_each` 時被復制。第一個版本可以工作,因為返回的函數對象(`counter` 的一份拷貝)被用來取出計數值。
### 從容器中取出某種類型的元素
下面是另一個好東西:一個從容器中取出某種類型元素的提取器。在把異類容器中的一部分傳遞給一個同類 容器時,這是一個有用的工具。手工來做這件事是乏味且容易出錯的,但一個簡單的函數對象可以為我們照看好一切。我們泛化這個函數對象,用取出元素的輸出迭 代器的類型,以及要從傳遞給該函數對象的 `any` 參數中取出的類型來參數化。
```
template <typename OutIt,typename Type> class extractor {
OutIt it_;
public:
extractor(OutIt it) : it_(it) {}
void operator()(boost::any& a) {
Type* t(boost::any_cast<Type>(&a));
if (t) {
*it_++ = *t;
}
}
};
```
為了更方便地創建一個取出器, 這里有一個函數,它可以推斷出輸出迭代器的類型,并返回一個相應的取出器.
```
template <typename Type, typename OutIt>
extractor<OutIt,Type> make_extractor(OutIt it) {
return extractor<OutIt,Type>(it);
}
```
### 使用謂詞和取出器
現在該用一個例程來測試一下我們新的 `any` 同伴了。
```
int main() {
std::cout << "Example of using predicates and the "
"function object any_counter\n";
std::vector<boost::any> vec;
vec.push_back(boost::any());
for(int i=0;i<10;++i) {
vec.push_back(i);
}
vec.push_back(boost::any());
```
我們把12個 `any` 對象加入到 `vec`, 現在我們想找出有多少個元素包含有值。為了計算含值元素的數量,我們使用前面創建的函數對象 `any_counter`。
```
// 計算含有值的any實例的數量
int i=std::for_each(
vec.begin(),
vec.end(),
any_counter()).count();
std::cout
<< "There are " << i << " non-empty any's in vec\n\n";
```
下面看操作一個 `any` 容器的取出器函數對象如何工作,它用來自源容器的特定類型的元素組成一個新的容器。
```
// 從vec中取出所有int
std::list<int> lst;
std::for_each(vec.begin(),vec.end(),
make_extractor<int>(std::back_inserter(lst)));
std::cout << "Found " << lst.size() << " ints in vec\n\n";
```
讓我們清除容器 `vec` 中的內容,再加一些新的值。
```
vec.clear();
vec.push_back(std::string("This is a string"));
vec.push_back(42);
vec.push_back(3.14);
```
現在,我們試用一下已創建的謂詞。首先,我們分別用兩個謂詞來顯示 `any` 是否包含一個 `string` 或一個 `int` 。
```
if (is_string(vec[0])) {
std::cout << "Found me a string!\n";
}
if (is_int(vec[1])) {
std::cout << "Found me an int!\n";
}
```
正如我們前面指出的,為每一種我們要用到的類型定義一個謂詞是乏味的,也是不必要的,我們只要簡單地使用我們的語言優勢。
```
if (contains<double>(vec[2])) {
std::cout <<
"The generic tool is sweeter, found me a double!\n";
}
}
```
運行這個例子,有如下輸出。
```
Example of using predicates and the function object any_counter
There are 10 non-empty any's in vec
Found 10 ints in vec
Found me a string!
Found me an int!
The generic tool is sweeter, found me a double!
```
象以上這些小而簡單的工具已經被證實是非常有用的。當然,不僅對 `any` 是這樣;它是標準庫容器和算法的設計中的一個特點。這些例子示范了如何與 `any` 一起使用組合函數。提供過濾、計數、操作特定類型等等,是隱藏實現細節的有效方法,并簡單化了對 `any` 的使用。
### 遵守標準庫適配器的要求
如果你覺得謂詞 `contains` 很有用,你可能要注意它并不是到處都能用。它不能和標準庫的適配器一起使用。下面的例子稍稍超出了本章的范圍,但由于 `any` 是那么地適用于容器類,所以留下 `contains` 謂詞的這點缺陷是不應該的。問題在于標準庫的適配器(`bind1st`, `bind2nd`, `not1`, 和 `not2`)利用了它們所適配的謂詞的一些必要條件。參數類型和結果類型必須用`typedef`暴露出來,這意味著我們需要的是函數對象而不是函數。
先來定義一個新的函數對象,`contains_t`. 它可以派生自輔助類 `std::unary_function` (它是C++標準庫的組成部分,以便創建正確的 `typedef`),自動地定義參數類型和結果類型,但為了讓事情更清楚,我們自己來提供所需的 `typedef` 。參數類型由 `const boost::any&` 改為 `boost::any`, 以避免產生到引用的引用,那是非法的。實現和前面的一樣,這里只給出函數調用操作符。
```
template <typename T> struct contains_t {
typedef boost::any argument_type;
typedef bool result_type;
bool operator()(boost::any a) const {
return typeid(T)==a.type();
}
};
```
為了保留名字 `contains` 以用在稍后的輔助函數,我們用 `contains_t` 作這個函數對象的名字。這里有一個輔助函數用來創建并返回一個自動設置為相應類型的 `contains_t` 實例。原因是我們想重載 `contains` ,以便我們還可以提供我們原來創建的謂詞。
```
template <typename T> contains_t<T> contains() {
return contains_t<T>();
}
```
最后,舊的謂詞被改為利用 `contains_t` 來實現。現在,如果我們為了某些原因要改變 `contains_t` 的實現,`contains` 可以反應出這些修改而無須更多的改進。
```
template <typename T> bool contains(const boost::any& a) {
return contains_t<T>()(a);
}
```
下面這個例程示范了我們已經得到的東西,包括新的函數對象和前例中的謂詞。
```
int main() {
std::cout << "Example of using the improved is_type\n";
std::vector<boost::any> vec;
vec.push_back(std::string("This is a string"));
vec.push_back(42);
vec.push_back(3.14);
```
使用的謂詞與前面沒有什么不同。測試一個 `any` 是否某種類型仍然很容易。
```
if (contains<double>(vec[2])) {
std::cout << "The generic tool has become sweeter! \n";
}
vec.push_back(2.52f);
vec.push_back(std::string("Another string"));
```
另一個使用 `contains` 的例子是,在一個容器中查找某種類型。這個例子查找第一個 `float`.
```
std::vector<boost::any>::iterator
it=std::find_if(vec.begin(),vec.end(),contains<float>());
```
現在,從一個中取回所含值的兩種方法都被示范出來。通過 `const` 引用傳遞 `any` 給 `any_cast` 的是異常拋出版本。傳遞 `any` 地址的版本則返回一個所存值的指針。
```
if (it!=vec.end()) {
std::cout << "\nPrint the float twice!\n";
std::cout << boost::any_cast<float>(*it) << "\n";
std::cout << *boost::any_cast<float>(&*it) << "\n";
}
std::cout <<
"There are " << vec.size() << " elements in vec\n";
```
我還沒有給出一個好的例子來說明為什么 `contains` 應該是一個發育完全的函數對象。在很多情形下,原因可能無法預先知道,因為我們不能預見我們的實現將會面對的每一種情形。一個強烈的原因是遵守標準庫的要求,更適用于我們已知的用例以外的情形。然而,我還是給你一個例子:任務是從一個容器 `vec` 中刪除所有不含 `string` 的元素。當然,另寫一個謂詞來做與 `contains` 相反的事情是一種方法,但這樣會很快導致維護的惡夢,因為類似作用的函數對象要不斷增生。標準庫提供給我們一個名為 `not1` 的適配器,它對一個函數對象的結果取反,它可以輕易地從我們的 `vector vec` 中清除所有非 `string` 元素。
```
vec.erase(std::remove_if(vec.begin(),vec.end(),
std::not1(contains<std::string>())),vec.end());
std::cout << "Now, there are only " << vec.size()
<< " elements left in vec!\n";
}
```
本節的例子示范了如何有效地使用 `any`. 因為所存值的類型不是 `any` 的類型的組成部分,要在不對所存類型強加要求(包括從同一基類派生)的前提下提供存儲,`any` 是一個基本工具。我們已經看到這個類型隱藏有某種價值。`any` 不允許在對值的類型不了解的情況下訪問所保存的值,限制了對所存值進行操作的機會。作為大的擴展,通過創建一些輔助類——謂詞和函數對象——提供所需的邏輯來訪問所存值,可以進行補償。
- 序
- 前言
- 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 總結