## weak_ptr
### 頭文件: `"boost/weak_ptr.hpp"`
`weak_ptr` 是 `shared_ptr` 的觀察員。它不會干擾`shared_ptr`所共享的所有權。當一個被`weak_ptr`所觀察的 `shared_ptr` 要釋放它的資源時,它會把相關的 `weak_ptr`的指針設為空。這防止了 `weak_ptr` 持有懸空的指針。你為什么會需要 `weak_ptr`? 許多情況下,你需要旁觀或使用一個共享資源,但不接受所有權,如為了防止遞歸的依賴關系,你就要旁觀一個共享資源而不能擁有所有權,或者為了避免懸空指針。可以從一個`weak_ptr`構造一個`shared_ptr`,從而取得對共享資源的訪問權。
以下是 `weak_ptr`的部分定義,列出并簡要介紹了最重要的函數。
```
namespace boost {
template<typename T> class weak_ptr {
public:
template <typename Y>
weak_ptr(const shared_ptr<Y>& r);
weak_ptr(const weak_ptr& r);
~weak_ptr();
T* get() const;
bool expired() const;
shared_ptr<T> lock() const;
};
}
```
### 成員函數
```
template <typename Y> weak_ptr(const shared_ptr<Y>& r);
```
這個構造函數從一個`shared_ptr`創建 `weak_ptr` ,要求可以從 `Y*` 隱式轉換為 `T*`. 新的 `weak_ptr` 被配置為旁觀 `r`所引向的資源。`r`的引用計數不會有所改變。這意味著`r`所引向的資源在被刪除時不會理睬是否有`weak_ptr` 引向它。這個構造函數不會拋出異常。
```
weak_ptr(const weak_ptr& r);
```
這個復制構造函數讓新建的 `weak_ptr`?旁觀與`weak_ptr r`<small class="calibre23">相關的shared_ptr</small>所引向的資源。<small class="calibre23">shared_ptr</small>的引用計數保持不變。這個構造函數不會拋出異常。
```
~weak_ptr();
```
`weak_ptr` 的析構函數,和構造函數一樣,它不改變引用計數。如果需要,析構函數會把 `*this` 與共享資源脫離開。這個析構函數不會拋出異常。
```
bool expired() const;
```
如果所觀察的資源已經"過期",即資源已被釋放,則返回 `True` 。如果保存的指針為非空,`expired` 返回 `false`. 這個函數不會拋出異常。
```
shared_ptr<T> lock() const
```
返回一個引向`weak_ptr`所觀察的資源的 `shared_ptr` ,如果可以的話。如果沒有這樣指針(即 `weak_ptr` 引向的是空指針),`shared_ptr` 也將引向空指針。否則,`shared_ptr`所引向的資源的引用計數將正常地遞增。這個函數不會拋出異常。
### 用法
我們從一個示范`weak_ptr`的基本用法的例子開始,尤其要看看它是如何不影響引用計數的。這個例子里也包含了 `shared_ptr`,因為 `weak_ptr` 總是需要和 `shared_ptr`一起使用的。使用 `weak_ptr` 要包含頭文件 `"boost/weak_ptr.hpp"`.
```
#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp"
#include <iostream>
#include <cassert>
class A {};
int main() {
boost::weak_ptr<A> w;
assert(w.expired());
{
boost::shared_ptr<A> p(new A());
assert(p.use_count()==1);
w=p;
assert(p.use_count()==w.use_count());
assert(p.use_count()==1);
// 從weak_ptr創建shared_ptr
boost::shared_ptr<A> p2(w);
assert(p2==p);
}
assert(w.expired());
boost::shared_ptr<A> p3=w.lock();
assert(!p3);
}
```
`weak_ptr w` 被缺省構造,意味著它初始時不旁觀任何資源。要檢測一個 `weak_ptr` 是否在旁觀一個活的對象,你可以使用函數 `expired`. 要開始旁觀,`weak_ptr` 必須要被賦值一個 `shared_ptr`. 本例中,`shared_ptr p` 被賦值給 `weak_ptr w`, 這等于說`p` 和 `w` 的引用計數應該是相同的。然后,再從`weak_ptr`構造一個`shared_ptr`,這是一種從`weak_ptr`那里獲得對共享資源的訪問權的方法。如果在構造`shared_ptr`時,`weak_ptr` 已經過期了,將從`shared_ptr`的構造函數里拋出一個 `boost::bad_weak_ptr` 類型的異常。再繼續,當 `shared_ptr p` 離開作用域,`w` 就變成過期的了。當調用它的成員函數 `lock` 來獲得一個`shared_ptr`時,這是另一種獲得對共享資源訪問權的方法,將返回一個空的 `shared_ptr` 。注意,從這個程序的開始到結束,`weak_ptr` 都沒有影響到共享對象的引用計數的值。
與其它智能指針不同的是,`weak_ptr` 不對它所觀察的指針提供重載的 `operator*` 和 `operator->`. 原因是對`weak_ptr`所觀察的資源的任何操作都必須是明顯的,這樣才安全;由于不會影響它們所觀察的共享資源的引用計數器,所以真的很容易就會不小心訪問到一個無效的指針。這就是為什么你必須要傳送 `weak_ptr` 給 `shared_ptr`的構造函數,或者通過調用`weak_ptr::lock`來獲得一個 `shared_ptr` 。這兩種方法都會使引用計數增加,這樣在 `shared_ptr` 從 `weak_ptr`創建以后,它可以保證共享資源的生存,確保在我們要使用它的時候它不會被釋放掉。
### 常見問題
由于在智能指針中保存的是指針的值而不是它們所指向的指針的值,因此在標準庫容器中使用智能指針有一個常見的問題,就是如何在算法中使用智能指針;算法通常需要訪問實際對象的值,而不是它們的地址。例如,你如何調用 `std::sort` 并正確地排序?實際上,這個問題與在容器中保存并操作普通指針是幾乎一樣的,但事實很容易被忽略(可能是由于我們總是避免在容器中保存裸指針)。當然我們 不能直接比較兩個智能指針的值,但也很容易解決。只要用一個解引用智能指針的謂詞就可以了,所以我們將創建一個可重用的謂詞,使得可以在標準庫的算法里使 用引向智能指針的迭代器,這里我們選用的智能指針是 `weak_ptr`。
```
#include <functional>
#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp"
template <typename Func, typename T>
struct weak_ptr_unary_t :
public std::unary_function<boost::weak_ptr<T>,bool> {
T t_;
Func func_;
weak_ptr_unary_t(const Func& func,const T& t)
: t_(t),func_(func) {}
bool operator()(boost::weak_ptr<T> arg) const {
boost::shared_ptr<T> sp=arg.lock();
if (!sp) {
return false;
}
return func_(*sp,t_);
}
};
template <typename Func, typename T> weak_ptr_unary_t<Func,T>
weak_ptr_unary(const Func& func, const T& value) {
return weak_ptr_unary_t<Func,T>(func,value);
}
```
`weak_ptr_unary_t` 函數對象對要調用的函數以及函數所用的參數類型進行了參數化。把要調用的函數保存在函數對象中使用使得這個函數對象很容易使用,很快我們就能看到這一點。為了使這個謂詞兼容于標準庫的適配器,`weak_ptr_unary_t` 要從 `std::unary_function`派生,后者保證了所有需要的 `typedefs` 都能提供(這些要求是為了讓標準庫的適配器可以這些函數對象一起工作)。實際的工作在調用操作符函數中完成,從`weak_ptr`創建一個 `shared_ptr` 。必須要確保在函數調用時資源是可用的。然后才可以調用指定的函數(或函數對象),傳入本次調用的參數(要解引用以獲得真正的資源) 和在對象中保存的值,這個值是在構造`weak_ptr_unary_t`時給定的。這個簡單的函數對象現在可以用于任意可用的算法了。為方便起見,我們還定義了一個助手函數,`weak_ptr_unary`, 它可以推出參數的類型并返回一個適當的函數對象\[14\]。我們來看看如何使用它。
> \[14\] 要使得這個類型更通用,還需要更多的設計。
```
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp"
int main() {
using std::string;
using std::vector;
using boost::shared_ptr;
using boost::weak_ptr;
vector<weak_ptr<string> > vec;
shared_ptr<string> sp1(
new string("An example"));
shared_ptr<string> sp2(
new string("of using"));
shared_ptr<string> sp3(
new string("smart pointers and predicates"));
vec.push_back(weak_ptr<string>(sp1));
vec.push_back(weak_ptr<string>(sp2));
vec.push_back(weak_ptr<string>(sp3));
vector<weak_ptr<string> >::iterator
it=std::find_if(vec.begin(),vec.end(),
weak_ptr_unary(std::equal_to<string>(),string("of using")));
if (it!=vec.end()) {
shared_ptr<string> sp(*++it);
std::cout << *sp << '\n';
}
}
```
本例中,創建了一個包含`weak_ptr`的 `vector`。最有趣的一行代碼(是的,它有點長)就是我們為使用`find_if`算法而創建`weak_ptr_unary_t`的那行。
```
vector<weak_ptr<string> >::iterator it=std::find_if(
vec.begin(),
vec.end(),
weak_ptr_unary(
std::equal_to<string>(),string("of using")));
```
通過把另一個函數對象,`std::equal_to`, 和一個用于匹配的`string`一起傳給助手函數`weak_ptr_unary`,創建了一個新的函數對象。由于 `weak_ptr_unary_t` 完全兼容于各種適配器(由于它是從`std::unary_function`派生而來的),我們可以再從它組合出各種各樣的函數對象。例如,我們也可以查找第一個不匹配`"of using"`的串:
```
vector<weak_ptr<string> >::iterator it=std::find_if(
vec.begin(),
vec.end(),
std::not1(
weak_ptr_unary(
std::equal_to<string>(),string("of using"))));
```
Boost智能指針是專門為了與標準庫配合工作而設計的。我們可以創建有用的組件來幫助我們可以更簡單地使用這些強大的智能指針。象 `weak_ptr_unary` 這樣的工具并不是經常要用到的;有一個庫提供了比`weak_ptr_unary`更好用的泛型綁定器\[15\]。弄懂這些智能指針的語義,可以讓我們更清楚地使用它們。
> <a>[15] 指Boost.Bind庫。
### 兩種從weak_ptr創建shared_ptr的慣用法
如你所見,如果你有一個旁觀某種資源的 `weak_ptr` ,你最終還是會想要訪問這個資源。為此,`weak_ptr` 必須被轉換為 `shared_ptr`, 因為 `weak_ptr` 是不允許訪問資源的。有兩種方法可以從`weak_ptr`創建`shared_ptr`:把 `weak_ptr` 傳遞給 `shared_ptr` 的構造函數,或者調用 `weak_ptr` 的成員函數`lock`, 它返回 `shared_ptr`. 選擇哪一個取決于你認為一個空的 `weak_ptr` 是錯誤的抑或不是。`shared_ptr` 構造函數在接受一個空的 `weak_ptr` 參數時會拋出一個 `bad_weak_ptr` 類型的異常。因此應該在你認為空的 `weak_ptr` 是一種錯誤時使用它。如果使用 `weak_ptr` 的函數 `lock`, 它會在`weak_ptr`為空時返回一個空的 `shared_ptr`。這在你想測試一個資源是否有效時是正確的,一個空的 `weak_ptr`?是預期中的。此外,如果使用 `lock`, 那么使用資源的正確方法應該是初始化并同時測試它,如下:
```
#include <iostream>
#include <string>
#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp"
int main() {
boost::shared_ptr<std::string>
sp(new std::string("Some resource"));
boost::weak_ptr<std::string> wp(sp);
// ...
if (boost::shared_ptr<std::string> p=wp.lock())
std::cout << "Got it: " << *p << '\n';
else
std::cout << "Nah, the shared_ptr is empty\n";
}
```
如你所見,[shared_ptr](../Text/content.html#ch01lev1sec6) `p` 被`weak_ptr wp` 的`lock`函數的結果初始化。然后 `p` 被測試,只有當它非空時資源才能被訪問。由于 `shared_ptr` 僅在這個作用域中有效,所以在這個作用域之外不會有機會讓你不小心用到它。另一種情形是當 `weak_ptr` 邏輯上必須非空的時候。那種情形下,不需要測試 `shared_ptr` 是否為空,因為 `shared_ptr` 的構造函數會在接受一個空`weak_ptr`時拋出異常,如下:
```
#include <iostream>
#include <string>
#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp"
void access_the_resource(boost::weak_ptr<std::string> wp) {
boost::shared_ptr<std::string> sp(wp);
std::cout << *sp << '\n';
}
int main() {
boost::shared_ptr<std::string>
sp(new std::string("Some resource"));
boost::weak_ptr<std::string> wp(sp);
// ...
access_the_resource(wp);
}
```
在這個例子中,函數 `access_the_resource` 從一個`weak_ptr`構造 `shared_ptr sp` 。這時不需要測試 `shared_ptr` 是否為空,因為如果 `weak_ptr` 為空,將會拋出一個 `bad_weak_ptr` 類型的異常,因此函數會立即結束;錯誤會在適當的時候被捕獲和處理。這樣做比顯式地測試 `shared_ptr` 是否為空然后返回要更好。這就是從`weak_ptr`獲得`shared_ptr`的兩種方法。
### 總結
`weak_ptr` 是Boost智能指針拼圖的最后一塊。`weak_ptr` 概念是`shared_ptr`的一個重要伙伴。它允許我們打破遞歸的依賴關系。它還處理了關于懸空指針的一個常見問題。在共享一個資源時,它常用于那些不參與生存期管理的資源用戶。這種情況不能使用裸指針,因為在最后一個 `shared_ptr` 被銷毀時,它會釋放掉共享的資源。如果使用裸指針來引用資源,將無法知道資源是否仍然存在。如果資源已經不存在,訪問它將會引起災難。通過使用 `weak_ptr`, 關于共享資源已被銷毀的信息會傳播給所有旁觀的 `weak_ptr`s,這意味著不會發生無意間訪問到無效指針的情形。這就象是觀察員模式(Observer pattern)的一個特例;當資源被銷毀,所有表示對此感興趣的都會被通知到。
對于以下情形使用 `weak_ptr` :
* 要打破遞歸的依賴關系
* 使用一個共享的資源而不需要共享所有權
* 避免懸空的指針
- 序
- 前言
- 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 總結