weak_ptr 是一種不控制所指向?qū)ο笊嫫诘闹悄苤羔槪赶蛴梢粋€(gè) shared_ptr 管理的對(duì)象,將一個(gè) weak_ptr 綁定到一個(gè) shared_ptr 不會(huì)改變 shared_ptr 的引用計(jì)數(shù),一旦最后一個(gè)指向?qū)ο蟮?shared_ptr 被銷毀,對(duì)象就會(huì)被釋放。即使有 weak_ptr 指向?qū)ο螅瑢?duì)象也還是會(huì)被釋放,因此, weak_ptr 的名字抓住了這種智能指針“弱”共享對(duì)象的特點(diǎn)。
| weak_ptr | 說明 |
|---|---|
| weak_ptr<T> w | 空 weak_ptr 可以指向類型為 T 的對(duì)象 |
| weak_ptr<T> w(sp) | 與 shared_ptr sp 指向相同對(duì)象的 weak_ptr,T 必須能轉(zhuǎn)換為 sp 指向的類型 |
| w = p | p 可以是一個(gè) shared_ptr 或一個(gè) weak_ptr,賦值后 w 與 p 共享對(duì)象 |
| w.reset() | 將 w 置為空 |
| w.use_count() | 與 w 共享對(duì)象的 shared_ptr 的數(shù)量 |
| w.expired() | 若 w.use_count() 為 0,返回 true,否則返回 false |
| w.lock() | 如果 expired 為 true,返回一個(gè)空 shared_ptr;否則返回一個(gè)指向 w 的對(duì)象的 shared_ptr |
當(dāng)我們創(chuàng)建一個(gè) weak_ptr 時(shí),要用一個(gè) shared_ptr 來初始化它:
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); //wp 弱共享 p;p 的引用計(jì)數(shù)未改變
本例中 wp 和 p 指向相同的對(duì)象,由于是弱共享,創(chuàng)建 wp 不會(huì)改變 p 的引用計(jì)數(shù);wp 指向的對(duì)象可能被釋放掉。
由于對(duì)象可能不存在,我們不能使用 weak_ptr 直接訪問對(duì)象,而必須調(diào)用 lock。此函數(shù)檢查 weak_ptr 指向的對(duì)象是否仍存在,如果存在,lock 返回一個(gè)指向共享對(duì)象的 shared_ptr。與任何其他 shared_ptr 類似,只要此 shared_ptr 存在,它所指向的底層對(duì)象也就會(huì)一直存在。例如:
if (shared_ptr<int> np = wp.lock()) { // 如果 np 不為空則條件成立
// 在 if 中,np 與 p 共享對(duì)象
}
在這段代碼中,只有當(dāng) lock 調(diào)用返回 true 時(shí)我們才會(huì)進(jìn)入 if 語句體。在 if 中,使用 np 訪問共享對(duì)象是安全的。
核查指針類
作為 weak_ptr 用途的一個(gè)展示,我們將為 StrBlob 類定義一個(gè)伴隨指針類。我們的指針類將命名為 StrBlobPtr,會(huì)保存一個(gè) weak_ptr,指向 StrBlob 的 data 成員,這是初始化時(shí)提供給它的,通過使用 weak_ptr,不會(huì)影響一個(gè)給定的 StrBlob 所指向的 vector 的生存期。但是,可以阻止用戶訪問一個(gè)不再存在的 vector 的企圖。
StrBlobPtr 會(huì)有兩個(gè)數(shù)據(jù)成員:wptr,或者為空,或者指向一個(gè) StrBlob 中的 vector;curr,保存當(dāng)前對(duì)象所表示的元素的下標(biāo),類似它的伴隨類 StrBlob,我們的指針類也有一個(gè) check 成員來檢查解引用 StrBlobPtr 是否安全:
// 對(duì)于訪問一個(gè)不存在元素的嘗試,StrBlobPtr 拋出一個(gè)異常
class StrBlobPtr {
public:
StrBlobPtr(): curr(0) {}
StrBlobPtr(StrBlob &a, size_t sz = 0):
wptr(a.data), curr(sz) {}
std::string & deref() const;
StrBlobPtr& incr(); // 前綴遞增
private:
// 若檢查成功,check 返回一個(gè)指向 vector 的 shared_ptr
std::shared_ptr<std::vector<std::string>>
check(std::size_t, const std::string& ) const;
// 保存一個(gè) weak_ptr,意味看底層 vector 可能會(huì)被銷毀
std::weak_ptr<std::vector<std::string>> wptr;
std:size_t curr; // 在數(shù)組中的當(dāng)前位置
}
默認(rèn)構(gòu)造函數(shù)生成一個(gè)空的 StrBlobPtr,其構(gòu)造函數(shù)初始化列表將 curr 顯式初始化為 0,并將 wptr 隱式初始化為一個(gè)空 weak_ptr,第二個(gè)構(gòu)造函數(shù)接受一個(gè) StrBlob 引用和一個(gè)可選的索引值,此構(gòu)造函數(shù)初始化 wptr,令其指向給定 StrBlob 對(duì)象的 shared_ptr 中的 vector,并將 curr 初始化為 sz 的值。我們使用了默認(rèn)參數(shù),表示默認(rèn)情況下將 curr 初始化為第一個(gè)元素的下標(biāo)。我們將會(huì)看到,StrBlob 的 end 成員將會(huì)用到參數(shù) sz。
值得注意的是,我們不能將 StrBlobPtr 綁定到一個(gè) const StrBlob 對(duì)象。這個(gè)限制是由于構(gòu)造函數(shù)接受一個(gè)非 const StrBlob 對(duì)象的引用而導(dǎo)致的。
StrBlobPtr 的 check 成員與 StrBlob 中的同名成員不同,它還要檢查指針指向的 vector 是否還存在:
std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(std::size_t i, const std::string &msg) const {
auto ret = wptr.lock(); // vector 還存在嗎?
if (!ret){
throw std::runtime_error("unbound StrBlobPtr");
}
if (i >= ret->size()){
throw std::out_of_range(msg);
}
return ret; // 否則,返回指向 vector 的 shared_ptr
}
由于一個(gè) weak_ptr 不參與其對(duì)應(yīng)的 shared_ptr 的引用計(jì)數(shù),StrBlobPtr 指向的 vector 可能已經(jīng)被釋放了。如果 vector 已銷毀,lock 將返回一個(gè)空指針。在本例中,任何 vector 的引用都會(huì)失敗,于是拋出一個(gè)異常。否則,check 會(huì)檢查給定索引,如果索引值合法,check 返回從 lock 獲得的 shared_ptr。
指針操作
我們將定義名為 deref 和 incr 的函數(shù),分別用來解引用和遞增 StrBlobPtr。
deref 成員調(diào)用 check,檢查使用 vector 是否安全以及 curr 是否在合法范圍內(nèi):
std::string& StrBlobPtr::deref() const {
auto p = check(curr, "dereference past end");
return (*p)[curr]; //(*p) 是對(duì)象所指向的 vector
}
如果 check 成功,p 就是一個(gè) shared_ptr,指向 StrBlobPtr 所指向的 vector。表達(dá)式 (*p)[curr] 解引用 shared_ptr 來獲得 vector,然后使用下標(biāo)運(yùn)算符提取并返回 curr 位置上的元素。
incr 成員也調(diào)用 check:
// 前綴遞增:返回遞增后的對(duì)象的引用
StrBlobPtr& StrBlobPtr::incr() {
// 如果 curr 已經(jīng)指向容器的尾后位置,就不能遞增它
check(curr, "increment past end of StrBlobPtr");
++curr; // 推進(jìn)當(dāng)前位置
return *this;
}
當(dāng)然,為了訪問 data 成員,我們的指針類必須聲明為 StrBlob 的 friend 我們還要為 StrBlob 類定義 begin 和 end 操作,返回一個(gè)指向它自身的 StrBlobPtr:
// 對(duì)于 StrBlob 中的友元聲明來說,此前置聲明是必要的
class StrBlobPtr;
class StrBlob {
friend class StrBlobPtr;
// 其他成員
// 返回指向首元素和尾后元素的 StrBlobPtr
StrBlobPtr begin() {return StrBlobPtr(*this); }
StrBlobPtr end() {
auto ret = StrBlobPtr(*this, data->size());
return ret;
}
};