在学习异常的时候,异常有一个很大的缺陷,就是异常会导致执行流乱跳.从而可能会导致某些资源没有被及时得到释放等等问题.
例如这样的问题:
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 };
总之,智能指针存在很大的缺陷,并且解决智能指针的隐患也不能得到有效的解决.
所以尽量不要用智能指针.