在學習異常的時候,異常有一個很大的缺陷,就是異常會導致執行流亂跳.從而可能會導致某些資源沒有被及時得到釋放等等問題.
例如這樣的問題:
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 };
總之,智能指針存在很大的缺陷,並且解決智能指針的隱患也不能得到有效的解決.
所以儘量不要用智能指針.