C++智能指針之AutoPtr

在學習異常的時候,異常有一個很大的缺陷,就是異常會導致執行流亂跳.從而可能會導致某些資源沒有被及時得到釋放等等問題.
例如這樣的問題:

  1 #include <iostream>                                                                                                    
  2 #include<vector>
  3 using namespace std;
  4 void fun()
  5 {
  6     int* arr = new int[10];
  7     cout<<"arr"<<endl;
  8     vector<int> v;
  9     v.at(0);  //at()函數在標準庫處理錯誤的方式是拋異常,拋異常後就會跳到test()函數中,就不會執行下面的代碼了,就導致了new出來的對象會存在內存泄漏
 10     delete[] arr;
 11     cout<<"釋放arr"<<endl;
 12 }
 13 void test()
 14 {
 15     try
 16     {
 17         fun();        
 18     }
 19     catch(exception e)
 20     {
 21         e.what();
 22     }
 23 }
 24 int main()
 25 {
 26     test();
 27     return 0;
 28 }
上面的例子就由於拋異常而導致的執行流亂跳而內存泄漏.

那麼,爲了解決這個問題,就出現了RAII.

RAII:Resource Acquisition Is Initialization資源分配即初始化.定義一個類來封裝資源的分配和釋放.在構造函數完成資源的分配和初始化,在析構函數中完成資源的清理,從而保證資源的正確初始化和釋放.

那麼,上面的問題我們就可以通過封裝一個類將析構和構造實現,然後在定義對象時就將其定義爲封裝的類的對象.

Auto_ptr.h
  1 #pragma once
  2 using namespace std;
  3     
  4 template<class T>
  5 class Auto_Ptr
  6 {   
  7     public:
  8         Auto_Ptr(T* _ptr)
  9             :ptr(_ptr)
 10         {}
 11         T& operator*()   //重載瞭解引用
 12         {
 13             return *ptr;
 14         }
 15         T* operator->() //重載了箭頭,從而可以像指針一樣使用.
 16         {
 17             return ptr;                                                                                                
 18         }
 19         ~Auto_Ptr() 
 20         {
 21             delete ptr;
 22             cout<<"釋放"<<endl;
 23         }
 24     protected:
 25         T* ptr;
 26 };  

test.cpp
  1 #include <iostream>                                                                                                    
  2 #include<vector>
  3 #include"Auto_Ptr.cpp"
  4 using namespace std;
  5 
  6 struct AA
  7 {
  8     int a;
  9     int b;
 10 };
 11 void fun()
 12 {
 13     Auto_Ptr<int> arr(new int(3));
 14     *arr = 5;
 15     cout<<"arr: "<<*arr<<endl<<arr.operator*()<<endl;
 16 
 17     Auto_Ptr<AA> a(new AA);   //自定義類型的智能指針
 18     (*a).a = 10;
 19     a->b = 20;
 20     cout<<"a:"<<(*a).a<<endl;
 21     cout<<"a:"<<a.operator*().a<<endl;
 22     cout<<"b:"<<a->b<<endl;
 23     cout<<"b:"<<a.operator->()->b<<endl;
 24     vector<int> v;
 25     v.at(0);
 26     cout<<"釋放arr"<<endl;
 27 }
 28 void test()
 29 {
 30     try
 31     {
 32         fun();        
 33     }
 34     catch(exception e)
 35     {
 36         e.what();
 37     }
 38 }
 39 int main()
 40 {
 41     test();
 42     return 0;
 43 }           

上面這個例子就是簡單的智能指針的實現,智能指針其實是RAII的一種思想.
①可以像指針一樣,然後對其進行使用
②智能,構造函數保存資源,析構函數釋放資源,不會存在內存泄漏.

可是,上面的例子中存在一個很大的陷阱,當把自定義類型通過原有的對象再拷貝構造一個對象時,程序就會崩潰.因爲這裏的拷貝構造是淺拷貝,兩個指針指向同一塊內存,釋放一塊空間兩次,就出現了bug.
解決:
方法1:深拷貝.手動實現一個拷貝構造的函數,重新開闢一段內存.這樣雖然可以解決問題,可是不符合我們的需求.拷貝構造本來就是想要指向同一塊內存,然後讓兩個指針管理同一塊內存.
方法2:引用計數寫時拷貝.這個方法就可以滿足我們的需求.

爲了解決智能指針由於拷貝構造和賦值運算符重載引起的問題:
方法1:管理權轉移法(如下圖所示,以及存在的問題)
這裏寫圖片描述
實現代碼如下:
模擬實現智能指針:

  1 #include <iostream>                                                                                                    
  2 using namespace std;
  3 
  4 template<class T>
  5 class Auto_Ptr
  6 {
  7     public:
  8         Auto_Ptr(T* _ptr)
  9             :ptr(_ptr)
 10         {}
 11         Auto_Ptr(Auto_Ptr<T>& p)
 12             :ptr(p.ptr)
 13         {
 14             p.ptr = NULL;
 15         }
 16         Auto_Ptr<T>& operator=(Auto_Ptr<T>& p)
 17         {
 18             if(this != &p)
 19             {
 20                 delete ptr;
 21                 ptr = p.ptr;
 22                 p.ptr = NULL;
 23             }
 24             return *this;
 25         }
 26         T& operator*()
 27         {
 28             return *ptr;
 29         }
 30         T* operator->()
 31         {
 32             return ptr;
 33         }
 34         ~Auto_Ptr()
 35         {
 36             if(ptr != NULL)
 37             {
 38                 delete ptr;
 39                 cout<<"釋放"<<endl;
 40             }
 41         }
 42     protected:
 43         T* ptr;
 44 };                   

方法2:
這裏寫圖片描述
實現的代碼如下:

  1 #include <iostream>
  2 using namespace std;
  3 
  4 template<class T>
  5 class Auto_Ptr
  6 {
  7     public:
  8         Auto_Ptr(T* _ptr)
  9             :ptr(_ptr)
 10              ,owner(true)
 11         {}                                                                                                             
 12         Auto_Ptr(Auto_Ptr<T>& p)
 13             :ptr(p.ptr)
 14         {
 15             owner = true;
 16             p.owner = false;
 17         }
 18         Auto_Ptr<T>& operator=(Auto_Ptr<T>& p)
 19         {
 20             if(this != &p)
 21             {
 22                 owner = true;
 23                 p.owner = false;
 24             }
 25             return *this;
 26         }
 27         T& operator*()
 28         {
 29             return *ptr;
 30         }
 31         T* operator->()
 32         {
 33             return ptr;
 34         }
 35         ~Auto_Ptr()
 36         {
 37             if(ptr != NULL && owner == true)
 38             {
 39                 delete ptr;
 40                 cout<<"釋放"<<endl;
 41             }
 42         }
 43     protected:
 44         T* ptr;
 45         bool owner;
 46 };                 

總之,智能指針存在很大的缺陷,並且解決智能指針的隱患也不能得到有效的解決.
所以儘量不要用智能指針.

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