## intrusive_ptr
### 頭文件: `"boost/intrusive_ptr.hpp"`
`intrusive_ptr` 是`shared_ptr`的插入式版本。有時我們必須使用插入式的引用計數智能指針。典型的情況是對于那些已經寫好了內部引用計數器的代碼,而我們又沒有時間去重寫它(或者已經不能獲得那些代碼了)。另一種情況是要求智能指針的大小必須與裸指針大小嚴格相等,或者`shared_ptr`的引用計數器分配嚴重影響了程序的性能(我可以肯定這是非常罕見的情況!)。從功能的觀點來看,唯一需要插入式智能指針的情況是,被指類的某個成員函數需要返回`this`,以便它可以用于另一個智能指針(事實上,也有辦法使用非插入式智能指針來解決這個問題,正如我們在本章前面看到的)。`intrusive_ptr` 不同于其它智能指針,因為它要求你來提供它所要的引用計數器。
當 `intrusive_ptr` 遞增或遞減一個非空指針上的引用計數時,它是通過分別調用函數 `intrusive_ptr_add_ref` 和 `intrusive_ptr_release`來完成的。這兩個函數負責確保引用計數的正確性,并且負責在引用計數降為零時刪除指針。因此,你必須為你的類重載這兩個函數,正如我們后面將看到的。
以下是`intrusive_ptr`的部分摘要,只列出了最重要的函數。
```
namespace boost {
template<class T> class intrusive_ptr {
public:
intrusive_ptr(T* p,bool add_ref=true);
intrusive_ptr(const intrusive_ptr& r);
~intrusive_ptr();
T& operator*() const;
T* operator->() const;
T* get() const;
operator unspecified-bool-type() const;
};
template <class T> T* get_pointer(const intrusive_ptr<T>& p);
template <class T,class U> intrusive_ptr<T>
static_pointer_cast(const intrusive_ptr<U>& r);
}
```
### 成員函數
```
intrusive_ptr(T* p,bool add_ref=true);
```
這個構造函數將指針`p`保存到`*this`中。如果 `p` 非空,并且 `add_ref` 為 `true`, 構造函數將調用 `intrusive_ptr_add_ref(p)`. 如果 `add_ref` 為 `false`, 構造函數則不調用 `intrusive_ptr_add_ref`. 如果`intrusive_ptr_add_ref`會拋出異常,則構造函數也會。
```
intrusive_ptr(const intrusive_ptr& r);
```
該復制構造函數保存一份`r.get()`的拷貝,并且如果指空非空則用它調用 `intrusive_ptr_add_ref` 。這個構造函數不會拋出異常。
```
~intrusive_ptr();
```
如果保存的指針為非空,則 `intrusive_ptr` 的析構函數會以保存的指針為參數調用函數 `intrusive_ptr_release`。 `intrusive_ptr_release` 負責遞減引用計數并在計數為零時刪除指針。這個函數不會拋出異常。
```
T& operator*() const;
```
解引用操作符返回所存指針的解引用。如果所存指針為空則會導致未定義行為。你應該確認`intrusive_ptr`有一個非空的指針,這可以用函數 `get` 實現,或者在Boolean上下文中測試 `intrusive_ptr` 。解引用操作符不會拋出異常。
```
T* operator->() const;
```
這個操作符返回保存的指針。在引用的指針為空時調用這個操作符會有未定義行為。這個操作符不會拋出異常。
```
T* get() const;
```
這個成員函數返回保存的指針。它可用于你需要一個裸指針的時候,即使保存的指針為空也可以調用。這個函數不會拋出異常。
```
operator unspecified-bool-type() const;
```
這個類型轉換函數返回一個可用于布爾表達式的類型,而它絕對不是 `operator bool`, 因為那樣會允許一些必須要禁止的操作。這個轉換允許`intrusive_ptr`在一個布爾上下文中被測試,例如,`if (p)`,? `p` 是一個 `intrusive_ptr`. 這個轉換函數當`intrusive_ptr`引向一個非空指針時返回`True`?; 否則返回 `false`. 這個轉換函數不會拋出異常。
### 普通函數
```
template <class T> T* get_pointer(const intrusive_ptr<T>& p);
```
這個函數返回 `p.get()`, 它主要用于支持泛型編程。\[10\] 它也可以用作替代成員函數 `get`, 因為它可以重載為可以與裸指針或第三方智能指針類一起工作。有些人寧愿用普通函數而不用成員函數。\[11\] 這個函數不會拋出異常。
> \[10\] 這種函數被稱為 shims. 見參考書目的 [12] 。
> \[11\]這種想法是出于以下原因,使用智能指針的成員函數時,很難分清它是操作智能指針還是操作它所指向的對象。例如, `p.get()` 和 `p->get()` 有完全不同的意思,不認真看還很難區別,而 `get_pointer(p)` 和 `p->get()` 則一看就知道不一樣。對于你來說這是不是問題,主要取決于你的感覺和經驗。
```
template <class T,class U>
intrusive_ptr<T> static_pointer_cast(const intrusive_ptr<U>& r);
```
這個函數返回 `intrusive_ptr<T>(static_cast<T*>(r.get()))`. 和 `shared_ptr`不一樣,你可以對保存在`intrusive_ptr`中的對象指針安全地使用`static_cast`。但是你可能出于對智能指針類型轉換的用法一致性而想使用這個函數。`static_pointer_cast` 不會拋出異常。
### 用法
使用`intrusive_ptr`與使用`shared_ptr`相比,有兩個主要的不同之處。第一個是你需要提供引用計數的機制。第二個是把`this`當成智能指針是合法的\[12\],正如我們即將看到的,有時候這樣很方便。注意,在多數情況下,應該使用非插入式的 `shared_ptr`.
> \[12\] 你不能用`shared_ptr` 來做到這一點,如果沒有進行特殊處理的話,如 `enable_shared_from_this`.
要使用 `boost::intrusive_ptr`, 要包含 `"boost/intrusive_ptr.hpp"` 并定義兩個普通函數 `intrusive_ptr_add_ref` 和 `intrusive_ptr_release`. 它們都要接受一個參數,即指向你要使用`intrusive_ptr`的類型的指針。這兩個函數的返回值被忽略。通常的做法是,泛化這兩個函數,簡單地調用被管理類型的成員函數去完成工作(例如,調用 `add_ref` 和 `release`)。如果引用計數降為零,`intrusive_ptr_release` 應該負責釋放資源。以下是你應該如何實現這兩個泛型函數的示范:
```
template <typename T> void intrusive_ptr_add_ref(T* t) {
t->add_ref();
}
template <typename T> void intrusive_ptr_release(T* t) {
if (t->release()<=0)
delete t;
}
```
注意,這兩個函數應該定義在它們的參數類型所在的作用域內。這意味著如果這個函數接受的參數類型來自 于一個名字空間,則函數也必須定義在那里。這樣做的原因是,函數的調用是非受限的,即允許采用參數相關查找,而如果有多個版本的函數被提供,那么全部名字 空間肯定不是放置它們的好地方。我們稍后將看到一個關于如何放置它們的例子,但首先,我們需要提供某類的引用計數器。
### 提供一個引用計數器
現在管理用的函數已經定義了,我們必須要提供一個內部的引用計數器了。在本例中,引用計數是一個初始化為零的私有數據成員,我們將公開 `add_ref` 和 `release` 成員函數來操作它。`add_ref` 遞增引用計數而 `release` 遞減它\[13\]。 我們可以增加一個返回引用計數當前值的成員函數,但`release`也可以做到這一點。下面的基類,`reference_counter`, 提供了一個計數器以及 `add_ref` 和 `release` 成員函數,我們可以簡單地用繼承來為一個類增加引用計數了。
> \[13\] 注意,在多線程環境下,對保持引用計數的變量的任何操作都必須同步化。
```
class reference_counter {
int ref_count_;
public:
reference_counter() : ref_count_(0) {}
virtual ~reference_counter() {}
void add_ref() {
++ref_count_;
}
int release() {
return --ref_count_;
}
protected:
reference_counter& operator=(const reference_counter&) {
// 無操作
return *this;
}
private:
// 禁止復制構造函數
reference_counter(const reference_counter&);
};
```
把`reference_counter`的析構函數聲明為虛擬的原因是這個類將被公開繼承,有可能會使用一個`reference_counter`指針來`delete`派生類。我們希望刪除操作能夠正確地調用派生類的析構函數。實現非常簡單:`add_ref` 遞增引用計數,`release` 遞減引用計數并返回它。要使用這個引用計數,要做的就是公共地繼承它。以下是一個類 `some_ class` ,包含一個內部引用計數,并使用 `intrusive_ptr`。
```
#include <iostream>
#include "boost/intrusive_ptr.hpp"
class some_class : public reference_counter {
public:
some_class() {
std::cout << "some_class::some_class()\n";
}
some_class(const some_class& other) {
std::cout << "some_class(const some_class& other)\n";
}
~some_class() {
std::cout << "some_class::~some_class()\n";
}
};
int main() {
std::cout << "Before start of scope\n";
{
boost::intrusive_ptr<some_class> p1(new some_class());
boost::intrusive_ptr<some_class> p2(p1);
}
std::cout << "After end of scope \n";
}
```
為了顯示 `intrusive_ptr`以及函數 `intrusive_ptr_add_ref` 和 `intrusive_ptr_release` 都正確無誤,以下是這個程序的運行輸出:
```
Before start of scope
some_class::some_class()
some_class::~some_class()
After end of scope
```
`intrusive_ptr` 為我們打點一切。當第一個 `intrusive_ptr p1` 創建時,它傳送了一個`some_class`的新實例。`intrusive_ptr` 構造函數實際上有兩個參數,第二個是一個 `bool` ,表示是否要調用 `intrusive_ptr_add_ref` 。由于這個參數的缺省值是 `True`, 所以在構造 `p1`時,`some_class`實例的引用計數變為1。然后,第二個 `intrusive_ptr`, `p2`初構造。它是從 `p1`復制構造的,當 `p2` 看到 `p1` 是引向一個非空指針時,它調用 `intrusive_ptr_add_ref`. 引用計數變為2。然后,兩個 `intrusive_ptr`都離開作用域了。首先, `p2` 被銷毀,析構函數調用 `intrusive_ptr_release`. 它把引用計數減為1。然后,`p1` 被銷毀,析構函數再次調用 `intrusive_ptr_release` ,導致引用計數降為0;這使得我們的`intrusive_ptr_release` 去 `delete` 該指針。你可能注意到 `reference_counter` 的實現不是線程安全的,因此不能用于多線程應用,除非加上同步化。
比起依賴于`intrusive_ptr_add_ref` 和 `intrusive_ptr_release`的泛型實現,我們最好有一些直接操作基類(在這里是 `reference_counter`)的函數。這樣做的優點在于,即使從`reference_counter`派生的類定義在其它的名字空間,`intrusive_ptr_add_ref` 和 `intrusive_ptr_release` 也還可以通過ADL (參數相關查找法)找到它們。修改`reference_counter`的實現很簡單。
```
class reference_counter {
int ref_count_;
public:
reference_counter() : ref_count_(0) {}
virtual ~reference_counter() {}
friend void intrusive_ptr_add_ref(reference_counter* p) {
++p->ref_count_;
}
friend void intrusive_ptr_release(reference_counter* p) {
if (--p->ref_count_==0)
delete p;
}
protected:
reference_counter& operator=(const reference_counter&) {
// 無操作
return *this;
}
private:
// 禁止復制構造函數
reference_counter(const reference_counter&);
};
```
### 把 this 用作智能指針
總的來說,提出一定要用插入式引用計數智能指針的情形是不容易的。大多數情況下,但不是全部情況下,非插入式智能指針都可以解決問題。但是,有一種情形使用插入式引用計數會更容易:當你需要從一個成員函數返回 `this` ,并把它存入另一個智能指針。當從一個被非插入式智能指針所擁有的類型返回 `this`時, 結果是有兩個不同的智能指針認為它們擁有同一個對象,這意味著它們會在某個時候一起試圖刪除同一個指針。這導致了兩次刪除,結果可能使你的應用程序崩潰。 必須有什么辦法可以通知另一個智能指針,這個資源已經被一個智能指針所引用,這正好是內部引用計數器(暗地里)可以做到的。由于 [intrusive_ptr](#ch01lev1sec8) 的邏輯不直接對它們所引向的對象的內部引用計數進行操作,這就不會違反所有權或引用計數的完整性。引用計數只是被簡單地遞增。
讓我們先看一下一個依賴于`boost::shared_ptr`來共享資源所有權的實現中潛在的問題。它基于本章前面討論[`enable_shared_from_this`](../Text/content.html#ch01lev2sec19)時的例子。
```
#include "boost/shared_ptr.hpp"
class A;
void do_stuff(boost::shared_ptr<A> p) {
// ...
}
class A {
public:
call_do_stuff() {
shared_ptr<A> p(???);
do_stuff(p);
}
};
int main() {
boost::shared_ptr<A> p(new A());
p->call_do_stuff();
}
```
類`A` 要調用函數 `do_stuff`, 但問題是 `do_stuff` 要一個 `shared_ptr<A>`, 而不是一個普通的`A`指針。因此,在 `A::call_do_stuff`里,應該如何創建 `shared_ptr` ?現在,讓我們重寫 `A` ,讓它兼容于 `intrusive_ptr`, 通過從 `reference_counter`派生,然后我們再增加一個 `do_stuff`的重載版本,接受一個 `intrusive_ptr<A>`類型的參數。
```
#include "boost/intrusive_ptr.hpp"
class A;
void do_stuff(boost::intrusive_ptr<A> p) {
// ...
}
void do_stuff(boost::shared_ptr<A> p) {
// ...
}
class A : public reference_counter {
public:
void call_do_stuff() {
do_stuff(this);
}
};
int main() {
boost::intrusive_ptr<A> p(new A());
p->call_do_stuff();
}
```
如你所見,在這個版本的 `A::call_do_stuff`里,我們可以直接把 this 傳給需要一個 `intrusive_ptr<A>`的函數,這是由于 `intrusive_ptr`的類型轉換構造函數。
最后,這里有一個特別的地方:現在 `A` 可以支持 `intrusive_ptr`了,我們也可以創建一個包裝`intrusive_ptr`的`shared_ptr`,這們我們就可以調用原來版本的 `do_stuff`, 它需要一個 `shared_ptr<A>` 作為參數。假如你不能控制 `do_stuff`的源碼,這可能是你要解決的一個非常真實的問題。這次,還是用定制刪除器的方法來解決,它需要調用 `intrusive_ptr_release`. 下面是一個新版本的 `A::call_do_stuff`.
```
void call_do_stuff() {
intrusive_ptr_add_ref(this);
boost::shared_ptr<A> p(this,&intrusive_ptr_release<A>);
do_stuff(p);
}
```
真是一個漂亮的方法。當沒有 `shared_ptr`剩下時,定制的刪除器被調用,它調用 `intrusive_ptr_release`, 遞減`A`的內部引用計數。注意,如果 `intrusive_ptr_add_ref` 和 `intrusive_ptr_release` 被實現為直接操作 `reference_counter`, 你就要這樣來創建 `shared_ptr` :
```
boost::shared_ptr<A> p(this,&intrusive_ptr_release);
```
### 支持不同的引用計數器
我們前面提過可以為不同的類型支持不同的引用計數。這在集成已有的采用不同引用計數機制的類時是有必要的(例如,第三方的類使用它們自己版本的引用計數器)。又或者對于資源的釋放有不同的需求,如調用`delete`以外的另一個函數。如前所述,對 `intrusive_ptr_add_ref` 和 `intrusive_ptr_release` 的調用是非受限的。這意味著在名字查找時要考慮參數(指針的類型)的作用域,從而這些函數應該與它們操作的類型定義在同一個作用域。如果你在全局名字空間里實現 `intrusive_ptr_add_ref` 和 `intrusive_ptr_release` 的泛型版本,你就不能在其它名字空間中再創建泛型版本了。例如,如果一個名字空間需要為它的所有類型定義一個特殊的版本,特化版本或重載版本必須提供給每 一個類型。否則,全局名字空間中的函數就會引起歧義。因此在全局名字空間中提供泛型版本不是一個好主意,而在其它名字空間中提供則可以。
既然我們已經用基類`reference_counter`實現了引用計數器,那么在全局名字空間中提供一個接受`reference_counter*`類型的參數的普通函數應該是一個好主意。這還可以讓我們在其它名字空間中提供泛型重載版本而不會引起歧義。例如,考慮`my_namespace`名字空間中的兩個類 `another_class` 和 `derived_class` :
```
namespace my_namespace {
class another_class : public reference_counter {
public:
void call_before_destruction() const {
std::cout <<
"Yes, I'm ready before destruction\n";
}
};
class derived_class : public another_class {};
template <typename T> void intrusive_ptr_add_ref(T* t) {
t->add_ref();
}
template <typename T> void intrusive_ptr_release(T* t) {
if (t->release()<=0) {
t->call_before_destruction();
delete t;
}
}
}
```
這里,我們實現了`intrusive_ptr_add_ref` 和 `intrusive_ptr_release`的泛型版本。因此我們必須刪掉在全局名字空間中的泛型版本,把它們替換為以一個`reference_counter`指針為參數的非模板版本。或者,我們干脆從全局名字空間中刪掉這些函數,也可以避免引起混亂。對于這兩個類 `my_namespace::another_class` 和 `my_namespace::derived_class`, 將調用這個特殊版本(那個調用了它的參數的成員函數 `call_before_destruction` 的版本)。其它類型或者在它們定義所在的名字空間中有相應的函數,或者使用全局名字空間中的版本,如果有的話。下面程序示范了這如何工作:
```
int main() {
boost::intrusive_ptr<my_namespace::another_class>
p1(new my_namespace::another_class());
boost::intrusive_ptr<A>
p2(new good_class());
boost::intrusive_ptr<my_namespace::derived_class>
p3(new my_namespace::derived_class());
}
```
首先,`intrusive_ptr p1` 被傳入一個新的 `my_namespace::another_class`實例。在解析對 `intrusive_ptr_add_ref`的調用時,編譯器會找到 `my_namespace`里的版本,即 `my_namespace::another_class*` 參數所在名字空間。因而,為那個名字空間里的類型所提供的泛型函數會被正確地調用。在查找 `intrusive_ptr_release`時也是同樣。然后,`intrusive_ptr p2` 被創建并被傳入一個類型`A` (我們早前創建的那個類型)的指針。那個類型是在全局名字空間里的,所以當編譯器試圖去找到函數 `intrusive_ptr_add_ref`的最佳匹配時,它只會找到一個版本,即接受`reference_counter`指針類型的那個版本(你應該記得我們已經從全局名字空間中刪掉了泛型版本)。因為 `A`?公共繼承自 `reference_counter`, 通過隱式類型轉換就可以進行正確的調用。最后,`my_namespace` 里的泛型版本被用于類 `my_namespace::derived_class`; 這與 `another_class`例子中的查找是一樣的。
這里最重要的教訓是,在實現函數 `intrusive_ptr_add_ref` 和 `intrusive_ptr_release`時,它們應該總是定義在它們操作的類型所在的名字空間里。從設計的角度來看,這也是完美的,把相關的東西放在一起,這有助于確保總是調用正確的版本,而不用擔心是否有多個不同的實現可供選擇。
### 總結
在多數情況下,你不應該使用 `boost::intrusive_ptr`, 因為共享所有權的功能已在 `boost::shared_ptr`中提供,而且非插入式智能指針比插入式智能指針更靈活。但是,有時候也會需要插入式的引用計數,可能是由于舊的代碼,或者是為了與第三方的類進行集成。當有這種需要時,可以用 `intrusive_ptr` ,它具有與其它Boost智能指針相同的語義。如果你使用過其它的Boost智能指針,你就會發現不論是否插入式的,所有智能指針都有一致的接口。使用`intrusive_ptr`的類必須可以提供引用計數。`ntrusive_ptr` 通過調用兩個函數,`intrusive_ptr_add_ref` 和 `intrusive_ptr_release`來管理引用計數;這兩個函數必須正確地操作插入式的引用計數,以保證 `intrusive_ptr`正確工作。在使用`intrusive_ptr`的類中已經內置有引用計數的情況下,實現對`intrusive_ptr`的支持就是實現這兩個函數。有些情況下,可以創建這兩個函數的參數化版本,然后對所有帶插入式引用計數的類型使用相同的實現。多數時候,聲明這兩個函數的最好的地方就是它們所支持的類型所在的名字空間。
在以下情況時使用 `intrusive_ptr` :
* 你需要把 `this` 當作智能指針來使用。
* 已有代碼使用或提供了插入式的引用計數。
* 智能指針的大小必須與裸指針的大小相等。
- 序
- 前言
- 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 總結