1. 程式人生 > >c++智慧指標的理解

c++智慧指標的理解

寫在前面:

智慧指標的運用是c++裡很重要的一個方面。

主要的理解:

智慧指標是為了更容易的動態的管理和使用動態記憶體而設計的,新的標準庫提供兩種智慧指標。
一個是shared_ptr 允許多個指標指向同一個物件
一個是unique_ptr 獨佔所指向的物件。
還有一種伴隨類 weak_ptr 他是一種弱引用 指向shared_ptr所指向的物件。
這些型別都定義在memory標頭檔案當中。

智慧指標類似容器也是模板,當我們建立一個智慧指標時候也需要給出指標所指向的物件的型別,這個在智慧指標的模板引數當中給出。

shared_ptr<string> p;

這就是聲明瞭一個指向string型別的智慧指標 預設初始化的智慧指標儲存的是一個空指標(預設無參建構函式)。智慧指標的使用和一般的指標是一樣的,解引用操作符使得返回指標指向的物件。條件判斷一個指標就是判斷這個指標是否為空。
智慧指標一樣支援指標的->獲得成員變數指向成員函式的操作符。(都已經過載)

智慧指標支援所有一般指標的操作但是也具備智慧指標獨有的操作:

make_shared<T>(args) 返回一個shared_ptr指向一個型別為T的物件,使用args初始化物件
shared_ptr<T>p(q) p是q的拷貝 q的計數器增加、
p = q 遞減p的計數器 遞增q的計數器
p.unique()
p.use_count()

最安全的分配和使用動態記憶體的額方式是使用make_shared的標準庫函式、
這個函式在定義一個動態指標的同時分配一個物件並且初始化他
使用一個auto 物件來儲存make_shared 的結果這種方式比較簡單

計數器概念

每一個shared_ptr都有一個與之相關聯的計數器稱為引用計數器。
無論何時我們拷貝一個shared_ptr都會使得計數器遞增。
當時用一個智慧指標去初始化另一個智慧指標的時候
當講其作為一個引數傳遞給一個函式的時候
作為函式返回值的時候
所關聯的計數器就會增加。

當我們給一個shared_ptr 賦予一個新的值的時候
或者當前的智慧指標被銷燬 (離開其作用域)
計數器就會減一

一旦一個智慧指標的計數器變為0 那麼就會自動的釋放自己所管理的物件
當銷燬此物件的時候是通過解構函式完成銷燬的工作。
解構函式就是釋放物件分配的資源
智慧指標的解構函式會遞減指向物件的引用計數,如果引用計數變為0 智慧指標的解構函式就會銷燬該物件
釋放其佔有的記憶體。

auto_ptr

一個簡易、通用的智慧指標,它不包含所有的小技巧,不像專用的或高效能的智慧指標那麼奢華,但是它可以很好的完成許多普遍的工作,它很適合日常性的使用。

auto_ptr所做的事情,就是動態分配物件以及當物件不再需要時自動執行清理。

void f()  
{  
T* pt( new T );  
...... 
      delete pt;  
} 

如果f()函式只有三行並且不會有任何意外,這麼做可能挺好的。
但是如果f()從不執行delete語句,或者是由於過早的返回,或者是由於執行函式體時丟擲了異常,那麼這個被分配的物件就沒有被刪除,從而我們產生了一個經典的記憶體洩漏
這裡採用的是new的方式構造那麼是在堆上構造的只有顯示的呼叫delete才能析構該物件 否則永遠都不能釋放該物件的記憶體。

解決方案:能讓示例安全的簡單辦法是把指標封裝在一個“智慧的”類似於指標的物件裡,這個物件擁有這個指標並且能在析構時自動刪除這個指標所指的物件。因為這個智慧指標可以簡單的當成一個自動的物件(它出了作用域時會自動毀滅),所以很自然的把它稱之為“智慧”指標。

void f()  
{  
auto_ptr<T> pt( new T );  
。。。。。。
} // 當pt出了作用域時解構函式被呼叫,從而物件被自動刪除  

現在程式碼不會洩漏T型別的物件,不管這個函式是正常退出還是丟擲了異常,因為pt的解構函式總是會在出棧時被呼叫,清理會自動進行。
最後,使用一個auto_ptr就像使用一個內建的指標一樣容易,而且如果想要“撤銷”資源,重新採用手動的所有權,我們只要呼叫release()。
現在將new的物件使用一個智慧指標進行管理,智慧指標在離開作用域,出棧的時候會自動呼叫解構函式,析構所指向的物件。

auto_ptr實現關鍵點:
1. 利用特點“棧上物件在離開作用範圍時會自動析構”。
2. 對於動態分配的記憶體,其作用範圍是程式設計師手動控制的,這給程式設計師帶來了方便但也不可避免疏忽造成的記憶體洩漏,畢竟只有編譯器是最可靠的。
3. auto_ptr通過在棧上構建一個物件a,物件a中wrap了動態分配記憶體的指標p,所有對指標p的操作都轉為對物件a的操作。而在a的解構函式中會自動釋放p的空間,而該解構函式是編譯器自動呼叫的,無需程式設計師操心。

不要誤用auto_ptr

1)auto_ptr不能共享所有權,即不要讓兩個auto_ptr指向同一個物件。

2)auto_ptr不能指向陣列,因為auto_ptr在析構的時候只是呼叫delete,而陣列應該要呼叫delete[]。

3)auto_ptr只是一種簡單的智慧指標,如有特殊需求,需要使用其他智慧指標,比如share_ptr。

4)auto_ptr不能作為容器物件,STL容器中的元素經常要支援拷貝,賦值等操作,在這過程中auto_ptr會傳遞所有權,那麼source與sink元素之間就不等價了。

用法一:  
std::auto_ptr<MyClass>m_example(new MyClass());  

用法二:  
std::auto_ptr<MyClass>m_example;  
m_example.reset(new MyClass());  

用法三(指標的賦值操作):  
std::auto_ptr<MyClass>m_example1(new MyClass());  
std::auto_ptr<MyClass>m_example2(new MyClass());  
m_example2=m_example1;  

在C++中,應絕對避免把auto_ptr放到容器中。即應避免下列程式碼:

vector<auto_ptr<MyClass>>m_example;

當用演算法對容器操作的時候,你很難避免STL內部對容器中的元素實現賦值傳遞,這樣便會使容器中多個元素被置位NULL,而這不是我們想看到的。

http://blog.csdn.net/monkey_d_meng/article/details/5901392
要避免這個問題,可以考慮使用採用了引用計數的智慧指標,例如boost::shared_ptr等。auto_ptr不會降低程式的效率,但auto_ptr不適用於陣列,auto_ptr根本不可以大規模使用。 shared_ptr也要配合weaked_ptr,否則會很容易觸發迴圈引用而永遠無法回收記憶體。 理論上,合理使用容器加智慧指標,C++可以完全避免記憶體洩露,效率只有微不足道的下降(中型以上程式最多百分之一)。