智能指針是什麼,他的真面目就是一個類生成的對象,這個類中包含了基本的重載->、*等一些指針形態的用法,最主要的是這個類裏面有一個指針參數:全部智能指針類中都有一個explicit構造函數,以指針做爲參數。好比auto_ptr的類模板原型爲:程序員
template<class T>數組
class auto_ptr{函數
explicit auto_ptr(T* p = 0);編碼
};spa
代碼中構造函數用了explicit關鍵字主要是防止隱式轉換,舉個例子:設計
auto_ptr<double> pd;指針
double *p_reg = new double;對象
pd = p_reg; //errorblog
pd = shared_ptr<double>(p_reg); //error生命週期
shared_ptr<double> pshared = p_reg; //error
shared_ptr<double> pshared(p_reg);
上面解釋了一下智能指針的本質是什麼,那它有什麼做用呢,看代碼看不出來,但仔細想,做爲一個類對象,建立時須要構造,那待生命週期結束,也是須要自動析構的,因此就帶出了他的功能,就是爲了保證其指向的New出來的對象,能夠在生命週期結束時自動Delete,做爲程序員咱們都知道,Delete這個東西咱們常常忘寫不說,也在不少return的地方忽略了寫Delete,這就致使了內存泄漏,很致命。
因此C++就提供了一種智能指針的模式去防止這種現象,現有最經常使用的智能指針,分別有 std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr。這6個智能指針都有各自的使用範疇,話很少說,研究下就知道。
⑴ std::auto_ptr
首先auto_ptr這個智能指針,屬於STL,同屬於STL的還有unique_ptr、shared_ptr等,感興趣的能夠去查查,但本文就不進行介紹了。先來看一段代碼
class Simple {
public:
Simple(int param = 0) {
number = param;
std::cout << "Simple: " << number << std::endl;
}
~Simple() {
std::cout << "~Simple: " << number << std::endl;
}
void PrintSomething() {
std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;
}
std::string info_extend;
int number;
};
void fun()
{
std::auto_ptr<Simple> ptr (new Simple(1));
if(ptr.get())
{
ptr->PrintSomething();
ptr.get()->info_extend = "Hello";
(*prt)->info_extend = "World";
}
}
上一段代碼運行的結果爲:
Simple:1
PrintSometing:
~Simple:1
這是最標準的一種智能指針的使用方法,對於auto_ptr這種智能指針也是徹底能駕馭的,但假如咱們在代碼中加入另外幾句
std::auto_ptr<Simple> ptr2;
ptr2 = ptr;
pt2->PrintSomething();
pt1->PrintSomething(); // 崩潰
若是將上面的代碼加到程序中就會崩潰,爲何呢?看源碼中,重載=時,進行了
reset(_Right.release());
繼續向裏執行release的代碼
能夠看到,這裏是將本來的指針控制權交給了別人,自身已經被置爲NULL了。因此咱們進行調用,不崩潰等什麼呢?
而後加另外一端代碼:
if (ptr.get()) {
ptr.release();
}
咱們能夠直接調用release函數,這就很尷尬了,直接返回值爲當前指向的控制權,但沒有指針接收,直接丟棄了,這就更尷尬了,致使只進行了構造,沒有析構。。因此真要使用release函數的話,必須這樣
Simple* ptr2 = ptr.release();
delete ptr2;
這噁心的用法總給人一種畫蛇添足的作法,因此我release函數的直接使用是沒有任何意義的。對於這種手動的釋放內存,直接調用
ptr.reset();
因此咱們對auto_ptr進行一個總結:
1,儘可能不要使用"="對智能指針進行賦值,若是使用,那先前對象就時一個空指針的。
2,release函數單獨使用沒有任何意義,不會釋放對象,還有可能致使對象丟失。他的做用僅僅是返回全部權。
3,不要把智能指針對象當作參數傳遞進入函數,由於也會調用release,致使調用完函數,回來對象已經被析構。
上述3個僅僅是我的發現的,對於auto_ptr自己就有不少不合理的地方,雖然還有人使用,但具體項目中用的已經很少了,因此就在auto_ptr的基礎上,增長了各類其餘的智能指針,用以修復這個使用不合理的地方。
⑵ boost::scoped_ptr
scoped_ptr屬於boost庫,他與auto_ptr相比,也是避免了上述幾個問題,主要的緣由就是scoped的設計爲獨享全部權。主要的體現就是調用release函數和‘=’的時候是會報錯的。雖然表面上的確是避免了auto_ptr中出現的問題,但種感受是一種逃避的方法,那就有了下面的另外一種智能指針。
⑶ boost::shared_ptr
scoped_ptr屬於boost庫,與上述的scoped_ptr相比,它添加了共享全部權的功能,也就是可使用‘=’方法,關於作法,原理就是在類中使用了一個引用計數,當進行賦值的時候對該數字加1,寫一段代碼看看:
void TestSharedPtr(boost::shared_ptr<Simple> ptr)
{
std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;
}
boost::shared_ptr<Simple> ptr(new Simple(1));
std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;
{
boost::shared_ptr<Simple> ptr2 = ptr;
std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;
TestSharedPtr(ptr);
std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;
}
std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;
這一段代碼運行下來的打印結果是:
TestSharedPtr2 UseCount: 1
TestSharedPtr2 UseCount: 2
TestSharedPtr2 UseCount: 3
TestSharedPtr2 UseCount: 2
TestSharedPtr2 UseCount: 1
大概解釋一下,在開始只有一個ptr時,打印出來的引用計數爲1,當另外使用一個ptr2時,再次打印出來爲2,意思就是當前共有兩個智能指針同時使用當前對象。進入函數後,又進行了一次拷貝構造到臨時變量,因此打印值又變成3,後續退出函數和退出ptr2的生命週期,引用計數的值各剪了1,當代碼執行完畢後,引用u計數值歸0,調用析構函數時使用Delete釋放空間。
⑷ boost::scoped_array
以數組的方式管理多個對象,但屬性與scoped_ptr徹底一致,獨享全部權,而其使用方法也與數組沒有太大的區別:
boost::scoped_array<Simple> ptr_array(new Simple[2]);
ptr_array[0].PrintSomething();
⑸ boost::shared_array
以數組的方式管理多個對象,屬性與shared_ptr王權一致,共享全部權,內部使用引用計數
⑹ boost::weak_ptr
weak_ptr是一中比較特殊的智能指針,其應用的場景比較單一,主要是對shared_ptr進行觀察的一種對象,也就是說用weak_ptr類型的對象對shared_ptr類型的對象進行引用,是能夠正常訪問,但不會改變後者的引用計數值,當後者被銷燬後,前者也就不能使用了。如今說說它的應用場景,若是子類中有一個shared_ptr類型的對象,那虛基類中就能夠定義一個weak_ptr類型的對象,經過訪問基類的weak_ptr對象是否爲空,就能夠判斷子類是否對本身進行賦值。
網上看了一些博客,發現weak_ptr還有一個很經常使用的場景,若是shared_ptr產生循環引用問題,就須要使用弱指針week與強引用指針相結合使用,什麼是循環引用問題呢?看段代碼:
{
shared_ptr<int> tr1(new Node());
shared_ptr<int> tr2(new Node());
tr1 -> next = tr2;
tr2 -> pre = tr1;
}
這段代碼若是運行到}處,致使tr1,tr2生命週期結束,須要本身調用析構函數時,就會發生崩潰,緣由是什麼呢?主要就是在調用析構,發現當前引用計數爲1,那就先去讓別人析構,但tr2的引用計數一樣爲2,兩個互相讓對方先析構,就陷入了一個的僵局,致使最後均不釋放,內存泄漏。對於這種狀況解決的辦法能夠有好幾種:
⑴ 作一個監控,當最後發現兩個智能指針陷入循環階段,手動打破僵局(定製一個定時器就能夠解決)
⑵ 模仿子類或父類的析構方式,若是陷入僵局,則先析構後構造的對象,好比先析構tr2,再析構tr1
⑶ 最簡單也是最經常使用的,使用弱引用型智能指針week_ptr,不會改變引用計數的值,當須要訪問的對象已經被析構,能夠經過lock()函數返回一個空的shared_ptr,因此就能夠自動處理了。因此只須要將上面的代碼tr2類型改成weel_ptr便可解決循環引用的問題。
本文有部分函數名和編碼方式是採用網上其餘博主的風格(由於以爲這種講解方法特別清晰,先簡單介紹,而後上代碼直接理解),因此有些抄襲的嫌疑,但所有均本人理解,並從新編碼以後寫上去的,不少地方也是本人經過理解源碼,寫出來的部分東西,若是有誤,請及時溝通修正。