正文
?????????智能指針能夠使C++的開發(fā)簡單化,主要是它能夠像其它限制性語言(如C#、VB)自動管理內存的釋放,而且能夠做更多的事情。
1、?什么是智能指針
智能指針是一種像指針的C++對象,但它能夠在對象不使用的時候自己銷毀掉。
我們知道在C++中的對象不再使用是很難定義的,因此C++中的資源管理是很復雜的。各種智能指針能夠操作不同的情況。當然,智能指針能夠在任務結束的時候刪除對象,除了在程序之外。
許多庫都提供了智能指針的操作,但都有自己的優(yōu)點和缺點。Boost庫是一個高質量的開源的C++模板庫,很多人都考慮將其加入下一個C++標準庫的版本中。
Boost提供了下面幾種智能指針:
shared_ptr
本指針中有一個引用指針記數器,表示類型T的對象是否已經不再使用。shared_ptr?是Boost中提供普通的智能指針,大多數地方都使用shared_ptr。
scoped_ptr
當離開作用域能夠自動釋放的指針。因為它是不傳遞所有權的。事實上它明確禁止任何想要這樣做的企圖!這在你需要確保指針任何時候只有一個擁有者時的任何一種情境下都是非常重要的。
intrusive_ptr
比?shared_ptr?更好的智能指針,但是需要類型?T?提供自己的指針使用引用記數機制。
weak_ptr
一個弱指針,幫助shared_ptr?避免循環(huán)引用。
shared_array
和?shared_ptr?類似,用來處理數組的。
scoped_array
和?scoped_ptr?類似,用類處理數組的。
?
下面讓我們看一個簡單的例子:
2、?首先介紹:boost::scoped_ptr
scoped_ptr?是?Boost?提供的一個簡單的智能指針,它能夠保證在離開作用域后對象被釋放。
例子說明:本例子使用了一個幫助我們理解的類:?CSample,?在類的構造函數、賦值函數、析構函數中都加入了打印調試語句。因此在程序執(zhí)行的每一步都會打印調試信息。在例子的目錄里已經包含了程序中需要的Boost庫的部分內容,不需要下載其它內容(查看Boost的安裝指南)。
下面的例子就是使用scoped_ptr?指針來自動釋放對象的:
使用普通指針
使用scoped_ptr?指針
void?Sample1_Plain()
{
??CSample * pSample(new?CSample);
??if?(!pSample->Query() )
??// just some function...
??{
????delete?pSample;
????return;
??}
??pSample->Use();
??delete?pSample;
}
#include?"boost/smart_ptr.h"
void?Sample1_ScopedPtr()
{
??boost::scoped_ptr
???????samplePtr(new?CSample);
??if (!samplePtr->Query() )
??// just some function...
????return;???
??samplePtr->Use();
}
?
使用普通普通指針的時候,我們必須記住在函數退出的時候要釋放在這個函數內創(chuàng)建的對象。當我們使用例外的時候處理指針是特別煩人的事情(容易忘記銷毀它)。使用scoped_ptr?指針就能夠在函數結束的時候自動銷毀它,但對于函數外創(chuàng)建的指針就無能為力了。
優(yōu)點:對于在復雜的函數種,使用scoped_ptr?指針能夠幫助我們處理那些容易忘記釋放的對象。也因此在調試模式下如果使用了空指針,就會出現一個斷言。
優(yōu)點
自動釋放本地對象和成員變量[1],延遲實例化,操作PIMPL和RAII(看下面)
缺點
在STL容器里,多個指針操作一個對象的時候需要注意。
性能
使用scoped_ptr?指針,會增加一個普通指針。
?
3、?引用指針計數器
引用指針計數器記錄有多少個引用指針指向同一個對象,如果最后一個引用指針被銷毀的時候,那么就銷毀對象本身。
shared_ptr?就是Boost中普通的引用指針計數器,它表示可以有多個指針指向同一個對象,看下面的例子:
void?Sample2_Shared()
{
??// (A)?創(chuàng)建Csample類的一個實例和一個引用。
??boost::shared_ptr
??printf("The Sample now has %i referencesn", mySample.use_count());?// The Sample now has 1 references
??// (B)?付第二個指針給它。
??boost::shared_ptr
??printf("The Sample now has %i referencesn", mySample.use_count());
??// (C)?設置第一個指針為空。
??mySample.reset();
??printf("The Sample now has %i referencesn", mySample2.use_count());??//?一個引用
??//?當mySample2離開作用域的時候,對象只有一個引用的時候自動被刪除。
}
?
在(A)中在堆棧重創(chuàng)建了CSample類的一個實例,并且分配了一個shared_ptr指針。對象mySample入下圖所示:
然后我們分配了第二個指針mySample2,現在有兩個指針訪問同一個數據。
我們重置第一個指針(將mySample設置為空),程序中仍然有一個Csample實例,mySample2有一個引用指針。
只要當最有一個引用指針mySample2退出了它的作用域之外,Csample這個實例才被銷毀。
當然,并不僅限于單個Csample這個實例,或者是兩個指針,一個函數,下面是用shared_ptr的實例:
·?????????用作容器中。
·?????????用在PIMPL的慣用手法 (the pointer-to-implementation idiom?)。
·?????????RAII(Resource-Acquisition-Is-Initialization)的慣用手法中。
·?????????執(zhí)行分割接口。
注意:如果你沒有聽說過PIMPL (a.k.a. handle/body)?和?RAII,可以找一個好的C++書,在C++中處于重要的內容,一般C++程序員都應該知道(不過我就是第一次看到這個寫法)。智能指針只是一中方便的他們的方法,本文中不討論他們的內容。
PIMPL:如果必須包容一個可能拋異常的子對象,但仍然不想從你自己的構造函數中拋出異常,考慮使用被叫做Handle Class或Pimpl的方法(“Pimpl”個雙關語:pImpl或“pointer to implementation”)
4、?主要特點
boost::shared_ptr?有一些重要的特征必須建立在其它操作之上。
·?????????shared_ptr
當聲明或定義一個shared_ptr
·?????????shared_ptr
在這里本質上不需要制定T的類型(如從一個基類繼承下來的)
·?????????shared_ptr
如果你的類中自己寫了釋放方法,也可以使用。具體參照Boost文檔。
·?????????強制轉換
如果你定義了一個U*能夠強制轉換到T*(因為T是U的基類),那么shared_ptr也能夠強制轉換到shared_ptr
·?????????shared_ptr?是線程安全的
(這種設計的選擇超過它的優(yōu)點,在多線程情況下是非常必要的)
·?????????已經作為一種慣例,用在很多平臺上,被證明和認同的。
5、?例子:在容器中使用shared_ptr
許多容器類,包括STL,都需要拷貝操作(例如,我們插入一個存在的元素到list,vector,或者container。)當拷貝操作是非常銷毀資源的時候(這些操作時必須的),典型的操作就是使用容器指針。
std::vector
vec.push_back( new CMyLargeClass("bigString") );
?
將內存管理的任務拋給調用者,我們能夠使用shared_ptr來實現。
typedef boost::shared_ptr
std::vector
vec.push_back( CMyLargeClassPtr(new CMyLargeClass("bigString")) );
?
當vector被銷毀的時候,這個元素自動被銷毀了。當然,除非有另一個智能指針引用了它,則還本能被銷毀。讓我們看Sample3中的使用:
void?Sample3_Container()
{
??typedef?boost::shared_ptr
??// (A) create a container of CSample pointers:
??std::vector
??// (B) add three elements
??vec.push_back(CSamplePtr(new?CSample));
??vec.push_back(CSamplePtr(new?CSample));
??vec.push_back(CSamplePtr(new?CSample));
??// (C) "keep" a pointer to the second:
??CSamplePtr anElement = vec[1];
??// (D) destroy the vector:
??vec.clear();
??// (E) the second element still exists
??anElement->Use();
??printf("done. cleanup is automaticn");
??// (F) anElement goes out of scope, deleting the last CSample instance
}
?
6、?使用Boost中的智能指針,什么是正確的使用方法
使用智能指針的一些操作會產生錯誤(突出的事那些不可用的引用計數器,一些對象太容易釋放,或者根本釋放不掉)。Boost增強了這種安全性,處理了所有潛在存在的危險,所以我們要遵循以下幾條規(guī)則使我們的代碼更加安全。
下面幾條規(guī)則是你應該必須遵守的:
規(guī)則一:賦值和保存?——?對于智能指針來說,賦值是立即創(chuàng)建一個實例,并且保存在那里?,F在智能指針擁有一個對象,你不能手動釋放它,或者取走它,這將幫助你避免意外地釋放了一個對象,但你還在引用它,或者結束一個不可用的引用計數器。
規(guī)則二:_ptr
·?????????當創(chuàng)建一個智能指針的時候需要明確寫出?__ptr
·?????????不能將T*賦值給一個智能指針。
·?????????不能寫ptr = NULL,應該使用ptr.reset()。
·?????????重新找回原始指針,使用ptr.get(),不必釋放這個指針,智能指針會去釋放、重置、賦值。使用get()僅僅通過函數指針來獲取原始指針。
·?????????不能通過T*指向函數指針來代表一個__ptr
·?????????這是一種特殊的方法來認定這個智能指針擁有的原始指針。不過在Boost:smart pointer programming techniques?舉例說明了許多通用的情況。
規(guī)則三:非循環(huán)引用?——?如果有兩個對象引用,而他們彼此都通過一個一個引用指針計數器,那么它們不能釋放,Boost?提供了weak_ptr來打破這種循環(huán)引用(下面介紹)。
規(guī)則四:非臨時的?share_ptr?——?不能夠造一個臨時的share_ptr來指向它們的函數,應該命名一個局部變量來實現。(這可以使處理以外更安全,Boost share_ptr best practices?有詳細解說)。
7、?循環(huán)引用
引用計數器是一種便利的資源管理機制,它有一個基本回收機制。但循環(huán)引用不能夠自動回收,計算機很難檢測到。一個最簡單的例子,如下:
struct?CDad;
struct?CChild;
typedef?boost::shared_ptr
typedef?boost::shared_ptr
struct?CDad : public CSample
{
???CChildPtr myBoy;
};
struct?CChild : public CSample
{
?CDadPtr myDad;
};
// a "thing" that holds a smart pointer to another "thing":
CDadPtr???parent(new?CDadPtr);
CChildPtr child(new?CChildPtr);
// deliberately create a circular reference:
parent->myBoy = child;
child->myDad = dad;
// resetting one ptr...
child.reset();
?
?????????parent?仍然引用CDad對象,它自己本身又引用CChild。整個情況如下圖所示:
如果我們調用dad.reset(),那么我們兩個對象都會失去聯系。但這種正確的離開這個引用,共享的指針看上去沒有理由去釋放那兩個對象,我們不能夠再訪問那兩個對象,但那兩個對象的確還存在,這是一種非常嚴重的內存泄露。如果擁有更多的這種對象,那么將由更多的臨界資源不能正常釋放。
???????如果不能解決好共享智能指針的這種操作,這將是一個嚴重的問題(至少是我們不可接受的)。因此我們需要打破這種循環(huán)引用,下面有三種方法:
A、???當只剩下最后一個引用的時候需要手動打破循環(huán)引用釋放對象。
B、???當Dad的生存期超過Child的生存期的時候,Child需要一個普通指針指向Dad。
C、??使用boost::weak_ptr打破這種循環(huán)引用。
方法A和B并不是一個完美的解決方案,但是可以在不使用weak_ptr的情況下讓我們使用智能指針,讓我們看看weak_ptr的詳細情況。
8、?使用weak_ptr跳出循環(huán)
強引用和弱引用的比較:
一個強引用當被引用的對象活著的話,這個引用也存在(就是說,當至少有一個強引用,那么這個對象就不能被釋放)。boost::share_ptr就是強引用。相對而言,弱引用當引用的對象活著的時候不一定存在。僅僅是當它存在的時候的一個引用。
boost::weak_ptr
struct CBetterChild : public CSample
{
??weak_ptr
??void BringBeer()
??{
????shared_ptr
????if (strongDad)??????????????????????// is the object still alive?
??????strongDad->SetBeer();
????// strongDad is released when it goes out of scope.
????// the object retains the weak pointer
??}
};
?
9、?Intrusive_ptr——輕量級共享智能指針
shared_ptr比普通指針提供了更完善的功能。有一個小小的代價,那就是一個共享指針比普通指針占用更多的空間,每一個對象都有一個共享指針,這個指針有引用計數器以便于釋放。但對于大多數實際情況,這些都是可以忽略不計的。
intrusive_ptr?提供了一個折中的解決方案。它提供了一個輕量級的引用計數器,但必須對象本身已經有了一個對象引用計數器。這并不是壞的想法,當你自己的設計的類中實現智能指針相同的工作,那么一定已經定義了一個引用計數器,這樣只需要更少的內存,而且可以提高執(zhí)行性能。
如果你要使用intrusive_ptr?指向類型T,那么你就需要定義兩個函數:intrusive_ptr_add_ref?和intrusive_ptr_release。下面是一個簡單的例子解釋如何在自己的類中實現:
#include "boost/intrusive_ptr.hpp"
// forward declarations
class CRefCounted;
namespace boost
{
????void intrusive_ptr_add_ref(CRefCounted * p);
????void intrusive_ptr_release(CRefCounted * p);
};
// My Class
class CRefCounted
{
??private:
????long????references;
????friend void ::boost::intrusive_ptr_add_ref(CRefCounted * p);
????friend void ::boost::intrusive_ptr_release(CRefCounted * p);
??public:
????CRefCounted() : references(0) {}???// initialize references to 0
};
// class specific addref/release implementation
// the two function overloads must be in the boost namespace on most compilers:
namespace boost
{
?inline void intrusive_ptr_add_ref(CRefCounted * p)
??{
????// increment reference count of object *p
????++(p->references);
??}
?inline void intrusive_ptr_release(CRefCounted * p)
??{
???// decrement reference count, and delete object when reference count reaches 0
???if (--(p->references) == 0)
?????delete p;
??}
} // namespace boost
?
????????
?????????這是一個最簡單的(非線程安全)實現操作。但作為一種通用的操作,如果提供一種基類來完成這種操作或許很有使用價值,也許在其他地方會介紹到。
10、?scoped_array?和?shared_array
scoped_array?和?shared_array和上面講的基本上相同,只不過他們是指向數組的。就像使用指針操作一樣使用operator new[]?,他們都重載了operator new[]。注意他們并不初始化分配長度。
11、?Boost的安裝
從www.boost.org上下載最新版本的boost,然后解壓縮到你指定的目錄里,解壓縮后的文件目錄如下:
Boost?????boost的源文件和頭文件。
Doc????????HTML格式的文檔。
Lib?????????庫文件(不是必需的)
…?????????????其他文件(“more”里有其他資料)
添加目錄到我們自己的IDE里:
VC6:在菜單Tools/Options,Directories tab, "Show Directories for... Include files",
VC7:?在菜單Tools/Options,??Projects/VC++ directories, "Show Directories for... Include files".
Boost的頭文件都在boost子目錄里,例如本文檔例子中有#include "boost/smart_ptr.hpp"。所以任何人當讀到年的源文件的時候就立刻知道你用到了boost中的智能指針。
12、?關于本文檔中的例子
本文檔中的例子里有一個子目錄boost僅僅包含了本例子中使用到的一些頭文件,僅僅是為了你編譯這個例子,如果你需要下載完整的boost或者獲取更多的資源請到www.boost.org。
13、?VC6中min/max的災難
當在VC中使用boost庫,或者其他庫的時候會有一些小的問題。
在Windows的頭文件中已經定義了min?和?max宏,所以在STL中的這兩個函數就調用不到了,例如在MFC中就是這樣,但是在Boost中,都是使用的std::命名空間下的函數,使用Windows的函數不能夠接受不同類型的參數在模板中使用,但是許多庫都依賴這些。
雖然Boost盡量處理這些問題,但有時候遇到這樣的問題的時候就需要在自己的代碼中加入像下面的代碼在第一個#include前加入#define _NOMINMAX。
#define _NOMINMAX????????????// disable windows.h defining min and max as macros
#include "boost/config.hpp"??// include boosts compiler-specific "fixes"
using std::min;??????????????// makle them globally available
using std::max;
?
?????????這樣操作并不是在任何時候都需要,而只有我們碰到使用了就需要加入這段代碼。
14、?資源
獲取更多的信息,或者有問題可以查找如下資源:
·?????????Boost home page
·?????????Download Boost
·?????????Smart pointer overview
·?????????Boost users mailing list
·?????????Boost中的智能指針(撰文??Bjorn Karlsson????翻譯??曾毅)
在你的代碼中使用Boost智能指針
Smart Pointers to boost your code(By peterchen)?
翻譯?masterlee
?
Download source files - 45.3kb
?
正文
?????????智能指針能夠使C++的開發(fā)簡單化,主要是它能夠像其它限制性語言(如C#、VB)自動管理內存的釋放,而且能夠做更多的事情。
?
?
1、?什么是智能指針
智能指針是一種像指針的C++對象,但它能夠在對象不使用的時候自己銷毀掉。
我們知道在C++中的對象不再使用是很難定義的,因此C++中的資源管理是很復雜的。各種智能指針能夠操作不同的情況。當然,智能指針能夠在任務結束的時候刪除對象,除了在程序之外。
許多庫都提供了智能指針的操作,但都有自己的優(yōu)點和缺點。Boost庫是一個高質量的開源的C++模板庫,很多人都考慮將其加入下一個C++標準庫的版本中。
?
Boost提供了下面幾種智能指針:
?
shared_ptr
本指針中有一個引用指針記數器,表示類型T的對象是否已經不再使用。shared_ptr?是Boost中提供普通的智能指針,大多數地方都使用shared_ptr。
scoped_ptr
當離開作用域能夠自動釋放的指針。因為它是不傳遞所有權的。事實上它明確禁止任何想要這樣做的企圖!這在你需要確保指針任何時候只有一個擁有者時的任何一種情境下都是非常重要的。
intrusive_ptr
比?shared_ptr?更好的智能指針,但是需要類型?T?提供自己的指針使用引用記數機制。
weak_ptr
一個弱指針,幫助shared_ptr?避免循環(huán)引用。
shared_array
和?shared_ptr?類似,用來處理數組的。
scoped_array
和?scoped_ptr?類似,用類處理數組的。
?
?
下面讓我們看一個簡單的例子:
?
2、?首先介紹:boost::scoped_ptr
scoped_ptr?是?Boost?提供的一個簡單的智能指針,它能夠保證在離開作用域后對象被釋放。
例子說明:本例子使用了一個幫助我們理解的類:?CSample,?在類的構造函數、賦值函數、析構函數中都加入了打印調試語句。因此在程序執(zhí)行的 每一步都會打印調試信息。在例子的目錄里已經包含了程序中需要的Boost庫的部分內容,不需要下載其它內容(查看Boost的安裝指南)。
?
下面的例子就是使用scoped_ptr?指針來自動釋放對象的:
?
使用普通指針
使用scoped_ptr?指針
void?Sample1_Plain()
{
??CSample * pSample(new?CSample);
?
??if?(!pSample->Query() )
??// just some function...
??{
????delete?pSample;
????return;
??}
?
??pSample->Use();
??delete?pSample;
}
#include?"boost/smart_ptr.h"
?
void?Sample1_ScopedPtr()
{
??boost::scoped_ptr
???????samplePtr(new?CSample);
?
??if (!samplePtr->Query() )
??// just some function...
????return;???
?
??samplePtr->Use();
?
}
?
使用普通普通指針的時候,我們必須記住在函數退出的時候要釋放在這個函數內創(chuàng)建的對象。當我們使用例外的時候處理指針是特別煩人的事情(容易忘記銷毀它)。使用scoped_ptr?指針就能夠在函數結束的時候自動銷毀它,但對于函數外創(chuàng)建的指針就無能為力了。
優(yōu)點:對于在復雜的函數種,使用scoped_ptr?指針能夠幫助我們處理那些容易忘記釋放的對象。也因此在調試模式下如果使用了空指針,就會出現一個斷言。
?
?
優(yōu)點
自動釋放本地對象和成員變量[1],延遲實例化,操作PIMPL和RAII(看下面)
缺點
在STL容器里,多個指針操作一個對象的時候需要注意。
性能
使用scoped_ptr?指針,會增加一個普通指針。
?
?
3、?引用指針計數器?
引用指針計數器記錄有多少個引用指針指向同一個對象,如果最后一個引用指針被銷毀的時候,那么就銷毀對象本身。
shared_ptr?就是Boost中普通的引用指針計數器,它表示可以有多個指針指向同一個對象,看下面的例子:
?
void?Sample2_Shared()
{
??// (A)?創(chuàng)建Csample類的一個實例和一個引用。
??boost::shared_ptr
??printf("The Sample now has %i referencesn", mySample.use_count());?// The Sample now has 1 references
??// (B)?付第二個指針給它。
??boost::shared_ptr
??printf("The Sample now has %i referencesn", mySample.use_count());
?
??// (C)?設置第一個指針為空。
??mySample.reset();
??printf("The Sample now has %i referencesn", mySample2.use_count());??//?一個引用
?
??//?當mySample2離開作用域的時候,對象只有一個引用的時候自動被刪除。
}
?
?
在(A)中在堆棧重創(chuàng)建了CSample類的一個實例,并且分配了一個shared_ptr指針。對象mySample入下圖所示:
?
?
?
然后我們分配了第二個指針mySample2,現在有兩個指針訪問同一個數據。
?
?
?
我們重置第一個指針(將mySample設置為空),程序中仍然有一個Csample實例,mySample2有一個引用指針。
?
?
?
只要當最有一個引用指針mySample2退出了它的作用域之外,Csample這個實例才被銷毀。
?
?
?
當然,并不僅限于單個Csample這個實例,或者是兩個指針,一個函數,下面是用shared_ptr的實例:
用作容器中。用在PIMPL的慣用手法 (the pointer-to-implementation idiom?)。RAII(Resource-Acquisition-Is-Initialization)的慣用手法中。執(zhí)行分割接口。
注意:如果你沒有聽說過PIMPL (a.k.a. handle/body)?和?RAII,可以找一個好的C++書,在C++中處于重要的內容,一般C++程序員都應該知道(不過我就是第一次看到這個寫 法)。智能指針只是一中方便的他們的方法,本文中不討論他們的內容。
PIMPL:如果必須包容一個可能拋異常的子對象,但仍然不想從你自己的構造函數中拋出異常,考慮使用被叫做Handle Class或Pimpl的方法(“Pimpl”個雙關語:pImpl或“pointer to implementation”)
?
4、?主要特點?
boost::shared_ptr?有一些重要的特征必須建立在其它操作之上。
shared_ptr