## shared_ptr
### 頭文件: `"boost/shared_ptr.hpp"`
幾乎所有稍微復雜點的程序都需要某種形式的引用計數智能指針。這些智能指針讓我們不再需要為了管理被兩個或多個對象共享的對象的生存期而編寫復雜的邏輯。當引用計數降為零,沒有對象再需要這個共享的對象時,這個對象就自動被銷毀了。引用計數智能指針可以分為插入式(intrusive)和非插入式(non-intrusive)兩 類。前者要求它所管理的類提供明確的函數或數據成員用于管理引用計數。這意味著在類的設計時就必須預見到它將與一個插入式的引用計數智能指針一起工作,或 者重新設計它。非插入式的引用計數智能指針對它所管理的類沒有任何要求。引用計數智能指針擁有與它所存指針有關的內存的所有權。沒有智能指針的幫助,對象 的共享會存在問題,必須有人負負責刪除共享的內存。誰負責?什么時候刪除?沒有智能指針,你必須在管理的內存之外增加生存期的管理,這意味著在各個擁有者 之間存在更強的依賴關系。換言之,沒有了重用性并增加了復雜性。
被管理的類可能擁有一些特性使得它更應該與引用計數智能指針一起使用。例如,它的復制操作很昂貴,或 者它所代表的有些東西必須被多個實例共享,這些特性都值得去共享所有權。還有一種情形是共享的資源沒有一個明確的擁有者。使用引用計數智能指針可以在需要 訪問共享資源的對象之間共享資源的所有權。引用計數智能指針還讓你可以把對象指針存入標準庫的容器中而不會有泄漏的風險,特別是在面對異常或要從容器中刪 除元素的時候。如果你把指針放入容器,你就可以獲得多態的好處,可以提高性能(如果復制的代價很高的話),還可以通過把相同的對象放入多個輔助容器來進行 特定的查找。
在你決定使用引用計數智能指針后,你應該選擇插入式的還是非插入式的?非插入式智能指針幾乎總是更好 的選擇,由于它們的通用性、不需要修改已有代碼,以及靈活性。你可以對你不能或不想修改的類使用非插入式的引用計數智能指針。而把一個類修改為使用插入式 引用計數智能指針的常見方法是從一個引用計數基類派生。這種修改可能比你想象的更昂貴。至少,它增加了相關性并降低了重用性。\[6\] 它還增加了對象的大小,這在一些特定環境中可能會限制其可用性。\[7\]
> \[6\] 考慮一下對同一個類型使用兩個以上引用計數智能指針的需要。如果兩個都是插入式的,兩個不同的基類可能會不兼容,而且也很浪費。如果其中一個是插入式的,那么使用非插入式的智能指針可以使基類的額外負擔為零。
> \[7\] 另一方面,非插入式智能指針要求額外的存儲用于智能指針本身。
`shared_ptr` 可以從一個裸指針、另一個`shared_ptr`、一個`std::auto_ptr`、或者一個`boost::weak_ptr`構造。還可以傳遞第二個參數給`shared_ptr`的構造函數,它被稱為刪除器(deleter)。刪除器稍后會被調用,來處理共享資源的釋放。這對于管理那些不是用`new`分配也不是用`delete`釋放的資源時非常有用(稍后將看到創建客戶化刪除器的例子)。`shared_ptr`被創建后,它就可象普通指針一樣使用了,除了一點,它不能被顯式地刪除。
以下是[shared_ptr](#ch01lev1sec6)的部分摘要;最重要的成員和相關普通函數被列出,隨后是簡單的討論。
```
namespace boost {
template<typename T> class shared_ptr {
public:
template <class Y> explicit shared_ptr(Y* p);
template <class Y,class D> shared_ptr(Y* p,D d);
~shared_ptr();
shared_ptr(const shared_ptr & r);
template <class Y> explicit
shared_ptr(const weak_ptr<Y>& r);
template <class Y> explicit shared_ptr(std::auto_ptr<Y>& r);
shared_ptr& operator=(const shared_ptr& r);
void reset();
T& operator*() const;
T* operator->() const;
T* get() const;
bool unique() const;
long use_count() const;
operator unspecified_bool_type() const; //譯注:原文是unspecified-bool-type(),有誤
void swap(shared_ptr<T>& b);
};
template <class T,class U>
shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r);
}
```
### 成員函數
```
template <class Y> explicit shared_ptr(Y* p);
```
這個構造函數獲得給定指針`p`的所有權。參數 `p` 必須是指向 `Y` 的有效指針。構造后引用計數設為1。唯一從這個構造函數拋出的異常是`std::bad_alloc` (僅在一種很罕見的情況下發生,即不能獲得引用計數器所需的自由空間)。
```
template <class Y,class D> shared_ptr(Y* p,D d);
```
這個構造函數帶有兩個參數。第一個是`shared_ptr`將要獲得所有權的那個資源,第二個是`shared_ptr`被銷毀時負責釋放資源的一個對象,被保存的資源將以`d(p)`的形式傳給那個對象。因此`p`的值是否有效取決于`d`。如果引用計數器不能分配成功,`shared_ptr`拋出一個類型為`std::bad_alloc`的異常。
```
shared_ptr(const shared_ptr& r);
```
`r`中保存的資源被新構造的`shared_ptr`所共享,引用計數加一。這個構造函數不會拋出異常。
```
template <class Y> explicit shared_ptr(const weak_ptr<Y>& r);
```
從一個[weak_ptr](../Text/content.html#ch01lev1sec9) (本章稍后會介紹)構造[shared_ptr](../Text/content.html#ch01lev1sec6)。這使得`weak_ptr`的使用具有線程安全性,因為指向`weak_ptr`參數的共享資源的引用計數將會自增(`weak_ptr`不影響共享資源的引用計數)。如果`weak_ptr`為空 (`r.use_count()==0`), `shared_ptr` 拋出一個類型為`bad_weak_ptr`的異常。
```
template <typename Y> shared_ptr(std::auto_ptr<Y>& r);
```
這個構造函數從一個`auto_ptr`獲取`r`中保存的指針的所有權,方法是保存指針的一份拷貝并對`auto_ptr`調用`release`。構造后的引用計數為1。而`r`當然就變為空的。如果引用計數器不能分配成功,則拋出 `std::bad_alloc` 。
```
~shared_ptr();
```
`shared_ptr`析構函數對引用計數減一。如果計數為零,則保存的指針被刪除。刪除指針的方法是調用`operator delete` 或者,如果給定了一個執行刪除操作的客戶化刪除器對象,就把保存的指針作為唯一參數調用這個對象。析構函數不會拋出異常。
```
shared_ptr& operator=(const shared_ptr& r);
```
賦值操作共享`r`中的資源,并停止對原有資源的共享。賦值操作不會拋出異常。
```
void reset();
```
`reset`函數用于停止對保存指針的所有權的共享。共享資源的引用計數減一。
```
T& operator*() const;
```
這個操作符返回對已存指針所指向的對象的一個引用。如果指針為空,調用`operator*` 會導致未定義行為。這個操作符不會拋出異常。
```
T* operator->() const;
```
這個操作符返回保存的指針。這個操作符與`operator*`一起使得智能指針看起來象普通指針。這個操作符不會拋出異常。
```
T* get() const;
```
`get`函數是當保存的指針有可能為空時(這時 `operator*` 和 `operator->` 都會導致未定義行為)獲取它的最好辦法。注意,你也可以使用隱式布爾類型轉換來測試 `shared_ptr` 是否包含有效指針。這個函數不會拋出異常。
```
bool unique() const;
```
這個函數在`shared_ptr`是它所保存指針的唯一擁有者時返回 `true` ;否則返回 `false`。 `unique` 不會拋出異常。
```
long use_count() const;
```
`use_count` 函數返回指針的引用計數。它在調試的時候特別有用,因為它可以在程序執行的關鍵點獲得引用計數的快照。小心地使用它,因為在某些可能的`shared_ptr`實現中,計算引用計數可能是昂貴的,甚至是不行的。這個函數不會拋出異常。
```
operator unspecified-bool-type() const;
```
這是個到`unspecified-bool-type`類型的隱式轉換函數,它可以在Boolean上下文中測試一個智能指針。如果`shared_ptr`保存著一個有效的指針,返回值為`True`;否則為`false`。注意,轉換函數返回的類型是不確定的。把返回類型當成`bool`用會導致一些荒謬的操作,所以典型的實現采用了safe bool idiom,\[8\] 它很好地確保了只有可適用的Boolean測試可以使用。這個函數不會拋出異常。
> \[8\] 由Peter Dimov發明的。
```
void swap(shared_ptr<T>& b);
```
這可以很方便地交換兩個`shared_ptr`。`swap` 函數交換保存的指針(以及它們的引用計數)。這個函數不會拋出異常。
### 普通函數
```
template <typename T,typename U>
shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r);
```
要對保存在`shared_ptr`里的指針執行`static_cast`,我們可以取出指針然后強制轉換它,但我們不能把它存到另一個`shared_ptr`里;新的 `shared_ptr` 會認為它是第一個管理這些資源的。解決的方法是用 `static_pointer_cast`. 使用這個函數可以確保被指物的引用計數保持正確。`static_pointer_cast` 不會拋出異常。
### 用法
使用`shared_ptr`解決的主要問題是知道刪除一個被多個客戶共享的資源的正確時機。下面是一個簡單易懂的例子,有兩個類 `A` 和 `B`, 它們共享一個`int`實例。使用 `boost::shared_ptr`, 你需要必須包含 `"boost/shared_ptr.hpp"`.
```
#include "boost/shared_ptr.hpp"
#include <cassert>
class A {
boost::shared_ptr<int> no_;
public:
A(boost::shared_ptr<int> no) : no_(no) {}
void value(int i) {
*no_=i;
}
};
class B {
boost::shared_ptr<int> no_;
public:
B(boost::shared_ptr<int> no) : no_(no) {}
int value() const {
return *no_;
}
};
int main() {
boost::shared_ptr<int> temp(new int(14));
A a(temp);
B b(temp);
a.value(28);
assert(b.value()==28);
}
```
類 `A` 和 `B`都保存了一個 `shared_ptr<int>`. 在創建 `A` 和 `B`的實例時,`shared_ptr temp` 被傳送到它們的構造函數。這意味著共有三個 `shared_ptr`:`a`, `b`, 和 `temp`,它們都引向同一個`int`實例。如果我們用指針來實現對一個的共享,`A` 和 `B` 必須能夠在某個時間指出這個`int`要被刪除。在這個例子中,直到`main`的結束,引用計數為3,當所有 `shared_ptr`離開了作用域,計數將達到0,而最后一個智能指針將負責刪除共享的 `int`.
### 回顧Pimpl用法
前一節展示了使用`scoped_ptr`的pimpl 用法,如果使用這種用法的類是不允許復制的,那么`scoped_ptr`在保存pimpl的動態分配實例時它工作得很好。但是這并不適合于所有想從pimpl用法中獲益的類型(注意,你還可以用 `scoped_ptr`,但必須手工實現復制構造函數和賦值操作符)。對于那些可以處理共享的實現細節的類,應該用 `shared_ptr`。當pimpl的所有權被傳遞給一個 `shared_ptr`, 復制和賦值操作都是免費的。你可以回憶起,當使用 `scoped_ptr` 去處理pimpl類的生存期時,對封裝類的復制是不允許的,因為 `scoped_ptr`是不可復制的。這意味著要使這些類支持復制和賦值,你必須手工定義復制構造函數和賦值操作符。當使用 `shared_ptr` 去處理pimpl類的生存期時,就不再需要用戶自己定義復制構造函數了。注意,這時pimpl實例是被該類的多個對象所共享,因此如果規則是每個pimpl實例只能被類的一個實例使用,你還是要手工編寫復制構造函數。解決的方法和我們在`scoped_ptr`那看到的很相似,只是把`scoped_ptr`換成了`shared_ptr`。
### shared_ptr 與標準庫容器
把對象直接存入容器中有時會有些麻煩。以值的方式保存對象意味著使用者將獲得容器中的元素的拷貝,對于那些復制是一種昂貴的操作的類型來說可能會有性能的問題。此外,有些容器,特別是 `std::vector`, 當你加入元素時可能會復制所有元素,這更加重了性能的問題。最后,傳值的語義意味著沒有多態的行為。如果你需要在容器中存放多態的對象而且你不想切割它 們,你必須用指針。如果你用裸指針,維護元素的完整性會非常復雜。從容器中刪除元素時,你必須知道容器的使用者是否還在引用那些要刪除的元素,不用擔心多 個使用者使用同一個元素。這些問題都可以用`shared_ptr`來解決。
下面是如何把共享指針存入標準庫容器的例子。
```
#include "boost/shared_ptr.hpp"
#include <vector>
#include <iostream>
class A {
public:
virtual void sing()=0;
protected:
virtual ~A() {};
};
class B : public A {
public:
virtual void sing() {
std::cout << "Do re mi fa so la";
}
};
boost::shared_ptr<A> createA() {
boost::shared_ptr<A> p(new B());
return p;
}
int main() {
typedef std::vector<boost::shared_ptr<A> > container_type;
typedef container_type::iterator iterator;
container_type container;
for (int i=0;i<10;++i) {
container.push_back(createA());
}
std::cout << "The choir is gathered: \n";
iterator end=container.end();
for (iterator it=container.begin();it!=end;++it) {
(*it)->sing();
}
}
```
這里有兩個類, `A` 和 `B`, 各有一個虛擬成員函數 `sing`. `B` 從 `A`公有繼承而來,并且如你所見,工廠函數 `createA` 返回一個動態分配的`B`的實例,包裝在`shared_ptr<A>`里。在 `main`里, 一個包含`shared_ptr<A>`的 `std::vector` 被放入10個元素,最后對每個元素調用`sing`。如果我們用裸指針作為元素,那些對象需要被手工刪除。而在這個例子里,刪除是自動的,因為在`vector`的生存期中,每個`shared_ptr`的引用計數都保持為1;當 `vector` 被銷毀,所有引用計數器都將變為零,所有對象都被刪除。有趣的是,即使 `A` 的析構函數沒有聲明為 `virtual`, `shared_ptr` 也會正確調用 `B`的析構函數!
上面的例子示范了一個強有力的技術,它涉及`A`里面的protected析構函數。因為函數 `createA` 返回的是 `shared_ptr<A>`, 因此不可能對`shared_ptr::get`返回的指針調用 `delete` 。這意味著如果為了向某個需要裸指針的函數傳送裸指針而從`shared_ptr`中取出裸指針的話,它不會由于意外地被刪除而導致災難。那么,又是如何允許 `shared_ptr` 刪除它的對象的呢? 這是因為指針指向的真正類型是 `B`; 而`B`的析構函數不是protected的。這是非常有用的方法,用于給`shared_ptr`中的對象增加額外的安全性。
### shared_ptr 與其它資源
有時你會發現你要把`shared_ptr`用于某個特別的類型,它需要其它清除操作而不是簡單的 `delete`.?`shared_ptr`可以通過客戶化刪除器來支持這種需要。那些處理象 `FILE*`這樣的操作系統句柄的資源通常要使用象`fclose`這樣的操作來釋放。要在`shared_ptr`里使用 `FILE*` ,我們要定義一個類來負責釋放相應的資源。
```
class FileCloser {
public:
void operator()(FILE* file) {
std::cout << "The FileCloser has been called with a FILE*, "
"which will now be closed.\n";
if (file!=0)
fclose(file);
}
};
```
這是一個函數對象,我們用它來確保在資源要釋放時調用 `fclose` 。下面是使用`FileCloser`類的示例程序。
```
int main() {
std::cout <<
"shared_ptr example with a custom deallocator.\n";
{
FILE* f=fopen("test.txt","r");
if (f==0) {
std::cout << "Unable to open file\n";
throw "Unable to open file";
}
boost::shared_ptr<FILE>
my_shared_file(f, FileCloser());
// 定位文件指針
fseek(my_shared_file.get(),42,SEEK_SET);
}
std::cout << "By now, the FILE has been closed!\n";
}
```
注意,在訪問資源時,我們需要對`shared_ptr`使用 `&*` 用法, `get`, 或 `get_pointer`。(請注意最好使用 `&*`. 另兩個選擇不太清晰) 這個例子還可以更簡單,如果我們在釋放資源時只需要調用一個單參數函數的話,就根本不需要創建一個客戶化刪除器類型。上面的例子可以重寫如下:
```
{
FILE* f=fopen("test.txt","r");
if (f==0) {
std::cout << "Unable to open file\n";
throw file_exception();
}
boost::shared_ptr<FILE> my_shared_file(f,&fclose);
// 定位文件指針
fseek(&*my_shared_file,42,SEEK_SET);
}
std::cout << "By now, the FILE* has been closed!\n";
```
定制刪除器在處理需要特殊釋放程序的資源時非常有用。由于刪除器不是 `shared_ptr` 類型的一部分,所以使用者不需要知道關于智能指針所擁有的資源的任何信息(當然除了如何使用它!)。例如,你可以使用對象池,定制刪除器只需簡單地把對象返還到池中。或者,一個 singleton 對象應該使用一個什么都不做的刪除器。
### 使用定制刪除器的安全性
我們已經看到對基類使用 protected 析構函數有助于增加使用`shared_ptr`的 類的安全性。另一個達到同樣安全級別的方法是,聲明析構函數為 protected (或 private) 并使用一個定制刪除器來負責銷毀對象。這個定制刪除器必須是它要刪除的類的友元,這樣它才可以工作。封裝這個刪除器的好方法是把它實現為私有的嵌套類,如 下例所示:
```
#include "boost/shared_ptr.hpp"
#include <iostream>
class A {
class deleter {
public:
void operator()(A* p) {
delete p;
}
};
friend class deleter;
public:
virtual void sing() {
std::cout << "Lalalalalalalalalalala";
}
static boost::shared_ptr<A> createA() {
boost::shared_ptr<A> p(new A(),A::deleter());
return p;
}
protected:
virtual ~A() {};
};
int main() {
boost::shared_ptr<A> p=A::createA();
}
```
注意,我們在這里不能使用普通函數來作為 `shared_ptr<A>` 的工廠函數,因為嵌套的刪除器是`A`私有的。使用這個方法,用戶不可能在棧上創建 `A`的對象,也不可能對`A`的指針調用 `delete` 。
### 從this創建shared_ptr??
有時候,需要從`this`獲得 `shared_ptr` ,即是說,你希望你的類被`shared_ptr`所管理,你需要把"自身"轉換為`shared_ptr`的方法。看起來不可能?好的,解決方案來自于我們即將討論的另一個智能指針[`boost::weak_ptr`](../Text/content.html#ch01lev2sec30).?`weak_ptr` 是 `shared_ptr`的一個觀察者;它只是安靜地坐著并看著它們,但不會影響引用計數。通過存儲一個指向`this`的 `weak_ptr` 作為類的成員,就可以在需要的時候獲得一個指向`this`的 `shared_ptr`。為了你可以不必編寫代碼來保存一個指向`this`的 `weak_ptr`,接著又從`weak_ptr`獲`shared_ptr`得,Boost.Smart_ptr 為這個任務提供了一個助手類,稱為 `enable_shared_from_this`. 只要簡單地讓你的類公有地派生自 `enable_shared_from_this`,然后在需要訪問管理`this`的`shared_ptr`時,使用函數 `shared_from_this` 就行了。下面的例子示范了如何使用 `enable_shared_from_this` :
```
#include "boost/shared_ptr.hpp"
#include "boost/enable_shared_from_this.hpp"
class A;
void do_stuff(boost::shared_ptr<A> p) {
...
}
class A : public boost::enable_shared_from_this<A> {
public:
void call_do_stuff() {
do_stuff(shared_from_this());
}
};
int main() {
boost::shared_ptr<A> p(new A());
p->call_do_stuff();
}
```
這個例子還示范了你要用`shared_ptr`管理`this`的情形。類 `A` 有一個成員函數 `call_do_stuff` 需要調用一個普通函數 `do_stuff`, 這個普通函數需要一個類型為 `boost:: shared_ptr<A>`的參數。現在,在 `A::call_do_stuff`里, `this` 不過是一個 `A`指針, 但由于 `A` 派生自 `enable_shared_from_this`, 調用 `shared_from_this` 將返回我們所要的 `shared_ptr` 。在`enable_shared_from_this`的成員函數 `shared_from_this`里,內部存儲的 `weak_ptr` 被轉換為 `shared_ptr`, 從而增加了相應的引用計數,以確保相應的對象不會被刪除。
### 總結
引用計數智能指針是非常重要的工具。Boost的 `shared_ptr` 提供了堅固而靈活的解決方案,它已被廣泛用于多種環境下。需要在使用者之間共享對象是常見的,而且通常沒有辦法通知使用者何時刪除對象是安全的。`shared_ptr` 讓使用者無需知道也在使用共享對象的其它對象,并讓它們 無需擔心在沒有對象引用時的資源釋放。這對于Boost的智能指針類而言是最重要的。你會看到Boost.Smart_ptr中還有其它的智能指針,但這 一個肯定是你最想要的。通過使用定制刪除器,幾乎所有資源類型都可以存入 `shared_ptr`。這使得`shared_ptr` 成為處理資源管理的通用類,而不僅僅是處理動態分配對象。與裸指針相比,`shared_ptr`會有一點點額外的空間代價。我還沒有發現由于這些代價太大而需要另外尋找一個解決方案的情形。不要去創建你自己的引用計數智能指針類。沒有比使用 `shared_ptr`智能指針更好的了。
在以下情況時使用 `shared_ptr` :
* 當有多個使用者使用同一個對象,而沒有一個明顯的擁有者時
* 當要把指針存入標準庫容器時
* 當要傳送對象到庫或從庫獲取對象,而沒有明確的所有權時
* 當管理一些需要特殊清除方式的資源時\[9\]
> [9] 通過定制刪除器的幫助。
- 序
- 前言
- 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 總結