C++ auto_ptr智能指針的用法

C++ auto_ptr智能指針的用法

 

C++中指針申請和釋放內存通常採用的方式是new和delete。然而標準C++中還有一個強大的模版類就是auto_ptr,它可以在你不用的時候自動幫你釋放內存。下面簡單說一下用法。

  1. 用法一:  
  2. std::auto_ptr<MyClass>m_example(new MyClass());  
  3.   
  4. 用法二:  
  5. std::auto_ptr<MyClass>m_example;  
  6. m_example.reset(new MyClass());  
  7.   
  8. 用法三(指針的賦值操作):  
  9. std::auto_ptr<MyClass>m_example1(new MyClass());  
  10. std::auto_ptr<MyClass>m_example2(new MyClass());  
  11. m_example2=m_example1;  

則C++會把m_example所指向的內存回收,使m_example1 的值爲NULL,所以在C++中,應絕對避免把auto_ptr放到容器中。即應避免下列代碼:

vector<auto_ptr<MyClass>>m_example;

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

 

雖然,標準auto_ptr智能指針機制很多人都知道,但很少使用它。這真是個遺憾,因爲auto_ptr優雅地解決了C++設計和編碼中常見的問題,正確地使用它可以生成健壯的代碼。本文闡述瞭如何正確運用auto_ptr來讓你的代碼更加安全——以及如何避免對auto_ptr危險但常見的誤用,這些誤用會引發間斷性發作、難以診斷的bug。

爲什麼稱它爲“自動”指針?auto_ptr只是衆多可能的智能指針之一。許多商業庫提供了更復雜的智能指針,用途廣泛而令人驚異,從管理引用的數量到提供先進的代理服務。可以把標準C++ auto_ptr看作智能指針的Ford Escort(elmar注:可能指福特的一種適合家居的車型):一個簡易、通用的智能指針,它不包含所有的小技巧,不像專用的或高性能的智能指針那麼奢華,但是它可以很好的完成許多普遍的工作,它很適合日常性的使用。

auto_ptr所做的事情,就是動態分配對象以及當對象不再需要時自動執行清理。這裏是一個簡單的代碼示例,沒有使用auto_ptr所以不安全:

  1. // 示例1(a):原始代碼    
  2. void f()  
  3. {  
  4. T* pt( new T );  
  5.       /*...更多的代碼...*/  
  6.       delete pt;  
  7. }  

我們大多數人每天寫類似的代碼。如果f()函數只有三行並且不會有任何意外,這麼做可能挺好的。但是如果f()從不執行delete語句,或者是由於過早的返回,或者是由於執行函數體時拋出了異常,那麼這個被分配的對象就沒有被刪除,從而我們產生了一個經典的內存泄漏。

能讓示例1(a)安全的簡單辦法是把指針封裝在一個“智能的”類似於指針的對象裏,這個對象擁有這個指針並且能在析構時自動刪除這個指針所指的對象。因爲這個智能指針可以簡單的當成一個自動的對象(這就是說,它出了作用域時會自動毀滅),所以很自然的把它稱之爲“智能”指針:

 
  1. // 示例1(b):安全代碼,使用了auto_ptr  
  2. void f()  
  3. {  
  4. auto_ptr<T> pt( new T );  
  5.       /*...更多的代碼...*/  
  6. // 酷:當pt出了作用域時析構函數被調用,從而對象被自動刪除  

現在代碼不會泄漏T類型的對象,不管這個函數是正常退出還是拋出了異常,因爲pt的析構函數總是會在出棧時被調用,清理會自動進行。

最後,使用一個auto_ptr就像使用一個內建的指針一樣容易,而且如果想要“撤銷”資源,重新採用手動的所有權,我們只要調用release()。

  1. // 示例2:使用一個auto_ptr  
  2. void g()  
  3. {  
  4. // 現在,我們有了一個分配好的對象  
  5. T* pt1 = new T;  
  6.       // 將所有權傳給了一個auto_ptr對象  
  7. auto_ptr<T> pt2(pt1);  
  8.       // 使用auto_ptr就像我們以前使用簡單指針一樣,  
  9. *pt2 = 12;          // 就像*pt1 = 12  
  10. pt2->SomeFunc(); // 就像pt1->SomeFunc();  
  11.       // 用get()來獲得指針的值  
  12. assert( pt1 == pt2.get() );  
  13.       // 用release()來撤銷所有權  
  14. T* pt3 = pt2.release();  
  15.       // 自己刪除這個對象,因爲現在沒有任何auto_ptr擁有這個對象  
  16. delete pt3;  
  17. // pt2不再擁有任何指針,所以不要試圖刪除它...OK,不要重複刪除  

最後,我們可以使用auto_ptr的reset()函數來重置auto_ptr使之擁有另一個對象。如果這個auto_ptr已經擁有了一個對象,那麼,它會先刪除已經擁有的對象,因此調用reset()就如同銷燬這個auto_ptr,然後新建一個並擁有一個新對象:

 

  1. // 示例 3:使用reset()  
  2. void h()  
  3. {  
  4. auto_ptr<T> pt( new T(1) );  
  5. pt.reset( new T(2) );         // 刪除由"new T(1)"分配出來的第一個T  
  6. // 最後pt出了作用域,第二個T也被刪除了  

auto_ptr用法:

1. 需要包含頭文件<memory>。

2. Constructor:explicit auto_ptr(X* p = 0) throw(); 將指針p交給auto_ptr對象託管。

3. Copy constructor:auto_ptr(const auto_ptr&) throw(); template<class Y> auto_ptr(const auto_ptr<Y>& a) throw(); 指針的託管權會發生轉移。

4. Destructor: ~auto_ptr(); 釋放指針p指向的空間。

5. 提供了兩個成員函數 X* get() const throw();       //返回保存的指針

6. 對象中仍保留指針 X* release() const throw();     //返回保存的指針,對象中不保留指針

 

auto_ptr實現關鍵點:

1. 利用特點“棧上對象在離開作用範圍時會自動析構”。

2. 對於動態分配的內存,其作用範圍是程序員手動控制的,這給程序員帶來了方便但也不可避免疏忽造成的內存泄漏,畢竟只有編譯器是最可靠的。

3. auto_ptr通過在棧上構建一個對象a,對象a中wrap了動態分配內存的指針p,所有對指針p的操作都轉爲對對象a的操作。而在a的析構函數中會自動釋放p的空間,而該析構函數是編譯器自動調用的,無需程序員操心。

多說無益,看一個最實用的例子:

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4. class TC  
  5. {  
  6.       public:  
  7.              TC(){cout<<"TC()"<<endl;}  
  8.              ~TC(){cout<<"~TC()"<<endl;}  
  9. };  
  10. void foo(bool isThrow)  
  11. {  
  12.      auto_ptr<TC> pTC(new TC);    // 方法2  
  13.      //TC *pTC = new TC;            // 方法1  
  14.      try  
  15.      {  
  16.                   if(isThrow)  
  17.                              throw "haha";  
  18.      }  
  19.      catch(const char* e)  
  20.      {  
  21.                  //delete pTC;           // 方法1  
  22.                  throw;  
  23.      }  
  24.      //delete pTC;              // 方法1  
  25. }  
  26. int main()  
  27. {  
  28.     try  
  29.     {  
  30.           foo(true);  
  31.     }  
  32.     catch(...)  
  33.     {  
  34.          cout<<"caught"<<endl;  
  35.     }  
  36.     system("pause");  
  37. }  

 1. 如果採用方案1,那麼必須考慮到函數在因throw異常的時候釋放所分配的內存,這樣造成的結果是在每個分支處都要很小心的手動 delete pTC;。

2. 如果採用方案2,那就無需操心何時釋放內存,不管foo()因何原因退出, 棧上對象pTC的析構函數都將調用,因此託管在之中的指針所指的內存必然安全釋放。

至此,智能指針的優點已經很明瞭了。

但是要注意使用中的一個陷阱,那就是指針的託管權是會轉移的。 例如在上例中,如果 auto_ptr<TC> pTC(new TC);   auto_ptr<TC> pTC1=pTC; 那麼,pTC1將擁有該指針,而pTC沒有了,如果再用pTC去引用,必然導致內存錯誤。

要避免這個問題,可以考慮使用採用了引用計數的智能指針,例如boost::shared_ptr等。auto_ptr不會降低程序的效率,但auto_ptr不適用於數組,auto_ptr根本不可以大規模使用。 shared_ptr也要配合weaked_ptr,否則會很容易觸發循環引用而永遠無法回收內存。 理論上,合理使用容器加智能指針,C++可以完全避免內存泄露,效率只有微不足道的下降(中型以上程序最多百分之一)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章