智能指針

http://wenku.baidu.com/view/b938fc6aa45177232f60a212.html 關於STL auto_ptr有講解

http://blog.csdn.net/NewNebuladream/article/details/4354664

如果要弄明白什麼叫智能指針,首先需要了解智能指針究竟是怎麼來的。

日常編程情景回顧

情景1共享所有權

 

Obj* fun(Obj *a)

{

Obj* b = a;

return b;

}

Obj i;

Obj* p1 = &i;

Obj* p2 = fun(p1);

p1->set(1);

delete p1;

……

p2->set(2);

 

在實際的編程當中,可能會遇到多個指針共享同一個內存區的情況。在上面的代碼當中指針p1p2指向同一塊內存區。這樣這塊內存何時釋放纔是合理的呢?這就要知道每一個引用這塊內存區對象的生命週期以便確定調用delete合適的時間,而這樣做無疑會增加程序的耦合度,使得程序的維護與升級變得更加複雜。如果稍不注意就會出現上面這段程序當中出現的問題--p1被釋放之後,p2就變成了一個野指針。p2->set(2)這段代碼還可以照常執行,只不過這段內存區域實際上是一個已經被程序釋放掉的垃圾區域。這樣的程序有時會正常的執行,但其中卻存在極大的安全隱患,程序的執行結果無法預計。

 

情景2異常安全

while(1)

{

Obj*p = new Obj();

try

{

p->pop();

...

delete p;

}

catch(expection e)

{

...

cout<<"Field";

}

    ...

}

 

對於上述代碼,動態的分配了一個Obj對象,如果在try模塊當中出現了異常問題,那麼在異常拋出時就不會執行delete p這條語句,即該對象的內存空間無法被刪除。但是在本次while執行後,p所指向的內存區域可能會被丟失(提供了垃圾回收機制也不能保證資源不會被丟失)。這樣猶豫內存的泄露,程序不僅可能耗盡系統的內存資源,也會對程序的運行狀態產生影響。

 

情景3避免編程中不良習慣導致的錯誤

new與delete這一對關鍵字不是在所有程序員的代碼中都會成對出現的。忘記執行delete可能會導致內存耗盡的問題。那麼可不可以有一種機制來保證,在離開對象作用域之後,其所指向的內存區域就會被自動刪除呢?

針對於上述的三個情景,程序員急切的需要安全與高效的智能指針來解決讓人頭疼的問題。

在C++標準庫當中,提供了一種智能指針叫做auto_ptr。但是其功能有限,僅僅可以解決一小部分的問題。在C++boost庫當中,提供了更加強大的Smart_ptr庫,其中包括四種智能指針:scoped_ptr,shared_ptr,instrusive_ptr以及weak_ptr。

獨霸資源的scoped_ptr

先說auto_ptr,它可以用來確保正確的刪除動態分配的內存對象,這一點其使用方法與scoped_ptr相同。在程序離開作用域之後,即使程序員不顯式的調用delete(如果使用,也不能調用delete),這個對象的內存區域也會被自動的解析掉。請看如下代碼:

#include "boost/scoped_ptr.hpp"

#include <string>

#include <iostream>

int main() 

{  

boost::scoped_ptr<std::string>  

p(new std::string("Use scoped_ptr often."));  // 打印字符串的值  

if (p)  

std::cout << *p << '/n';     

// 獲取字符串的大小

size_t i=p->size();  

// 給字符串賦新值  

*p="Acts just like a pointer";   

// 這裏p被銷燬,並刪除std::string 

...

}

再來看一個auto_ptr的例子:

auto_ptr<Obj> p;

auto_ptr<Obj> another_p = p;

another_p->set(1);

p->set(2);

如果有人膽敢寫出上述代碼,那麼程序難逃崩潰的命運。原因很簡單,在執行了語句

auto_ptr<Obj> another_p = p;

之後,指針p就被自動析構掉了,變成了一個空指針。p把他的所有權轉移給了another_p。但是這種行爲會讓很多程序員使用起來很不爽,這樣才引發了scoped_ptr的誕生。

使用boost::scoped_ptr的時候必須注意,它不允許進行復制操作,一旦聲明瞭一個指向某內存空間的指針,那麼就不可以通過another_p = p;的方式來分配內存空間的新所有權。

 

號稱最最智能的shared_ptr

shared_ptr號稱是boost庫當中最最智能的指針。它實現了一種對於資源共享對象的非侵入式計數機制。說到這裏要先介紹兩個概念:

侵入式

侵入式的引用計數管理要求資源對象本身維護引用計數,同時提供增減引用計數的管理接口。通常侵入式方案會提供配套的侵入式引用計數智能指針。該智能指針通過調用資源對象的引用計數管理接口來自動增減引用計數。COM對象與CComPtr便是侵入式引用計數的一個典型實例。

非侵入式

非侵入式的引用計數管理對資源對象本身沒有任何要求,而是完全藉助非侵入式引用計數智能指針在資源對象外部維護獨立的引用計數。shared_ptr便是基於這個思路。

(引自http://blog.liancheng.info/?p=85

如果要實現多個指針對象共享內存,scope_ptr顯然無能爲力,而使用裸指針又要存在共享內存區何時釋放的問題。要解決這個問題,還需要另外編寫一個內存管理器,這意味着內存共享者之間的依賴關係會被加強,導致代碼失去重用性,且增加了複雜性。而shared_ptr的出現就是爲了解決上述問題。由於採用了非侵入式的方法,程序計數會交給shared_ptr自動執行,資源本身並不清楚被引用的次數。這樣一方面可以解決前面我們碰到的部分問題,而且簡化了計數等管理操作,但同時它還會引發新的問題。請看下面一段代碼:

boost::asio::io_service io_service;

typedef boost::shared_ptr<client> Client;

vector<boost::shared_ptr<client>> vt;

while(i < 1000)

{

i++;

 

boost::shared_ptr<client> Client(new client(io_service, argv[1], argv[2]));

vt.push_back(Client);

}

boost::thread t(boost::bind(&boost::asio::io_service::run,&io_service));

t.join();

io_service.run();

return 0;

這裏的vector vt本身沒有任何邏輯上的用途,但是如果不加上這一條語句,由於client對象被一個shared_ptr所引用了,故在while執行完後,此內存空間就會因爲引用對象數量爲0而被解析掉。這樣程序的執行就會出現異常錯誤。爲了避免異常的出現,我們就不得不另外加上一個“多餘的”變量,來共享對象。這樣一來,我們的程序就違背了使用智能指針的初衷--我們已經無法完全的把內存釋放這一棘手的問題完全交給智能指針解決了。

談到這裏不得不談一下shared_ptr使用過程當中所存在的一個問題。對於某一個聲明爲shared_ptr的資源對象,由於其生命期依賴於其引用對象,這樣只要有引用對象要使用這個資源,就一定要聲明爲shared_ptr類型的指針。如此,就會導致這種shared_ptr不斷地向程序當中的其它去角落擴展,只要原始資源還存在利用價值,這種擴展就不會停止。

最後要說的就是如果要從this創建一個shared_ptr,也就是說在資源的內部創建它自己的shared_ptr,應該如何實現呢?在《beyond the C++ Standard library》一書當中,提到了boost中的兩種實現機制。一種是通過引入一個weak_ptr觀察指針來實現,這個我們以後再詳細討論。另外一種就是通過其提供的一個稱爲enable_shared_from_this的輔助類,只需要通過對於這個類顯式的繼承就可以實現上述的功能。但是,這樣的繼承體系就是對於資源對象的一種要求,無形中又將問題轉換成了侵入式的問題。

對於boost庫當中智能指針暫時先研究到這裏,更加深入的內容稍後待續。總之一點,雖然智能指針可以帶來很多內存管理上的便利,但是它看起來並不是一種“萬能的”解決方案,內存管理當中的一切問題也不可能全部交給它來完成,更加複雜的問題還需要我們程序員開動腦筋去解決。

好,今天的C++科普總結先到這裏,歡迎繼續關注。

 

發佈了35 篇原創文章 · 獲贊 5 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章