智能指针的原理及实现
智能指针
1 智能指针的作用
智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源
2 智能指针的种类
shared_ptr、unique_ptr、weak_ptr、auto_ptr
(1) shared_ptr
实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。
-
智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针;
-
每次创建类的新对象时,初始化指针并将引用计数置为1;
-
当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;
-
对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;
-
调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
(2)unique_ptr
unique\_ptr采用的是独享所有权语义,一个非空的unique\_ptr总是拥有它所指向的资源。转移一个unique\_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;所以unique\_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);如果你拷贝一个unique\_ptr,那么拷贝结束后,这两个unique\_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。
(3)weak_ptr
weak\_ptr:弱引用。 引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak\_ptr打破环形引用。**weak\_ptr**是一个弱引用,它是为了配合shared\_pt而引入的一种智能指针,它指向一个由shared\_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。如果一块内存被shared\_ptr和weak\_ptr同时引用,当所有shared\_ptr析构了之后,不管还有没有weak\_ptr引用该内存,内存也会被释放。所以weak\_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak\_ptr是否为空指针。
(4)auto_ptr
auto_ptr不支持拷贝和赋值操作,不能用在STL标准容器中。STL容器中的元素经常要支持拷贝、赋值操作,在这过程中auto_ptr会传递所有权,auto_ptr采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个auto_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空。
智能指针代码实现:用两个类来实现智能指针的功能,一个是引用计数类,另一个则是指针类。
(1) 引用计数类
`
1. // 引用计数器类 用于存储指向同一对象的指针数
2. template<typename T>
3. class Counter
4. {
5. private:
6. // 数据成员
7. T *ptr; // 对象指针
8. int cnt; // 引用计数器
10. // 友元类声明
11. template<typename T>
12. friend class SmartPtr;
14. // 成员函数
15. // 构造函数
16. Counter(T *p) // p为指向动态分配对象的指针
17. {
18. ptr = p;
19. cnt = 1;
20. }
21. // 析构函数
22. ~Counter()
23. {
24. delete ptr;
25. }
26. };
`
(2) 指针类
`
1. // 智能指针类
2. template<typename T>
3. class SmartPtr
4. {
5. private:
6. // 数据成员
7. Counter<T> *ptr_cnt; //
9. public:
11. // 成员函数
12. // 普通构造函数 初始化计数类
13. SmartPtr(T *p)
14. {
15. ptr_cnt = new Counter<T>(p);
16. }
17. // 拷贝构造函数
18. SmartPtr(const SmartPtr &other)
19. {
20. ptr_cnt = other.ptr_cnt;
21. ptr_cnt->cnt++;
22. }
23. // 赋值运算符重载函数
24. SmartPtr &operator=(const SmartPtr &rhs)
25. {
26. ptr_cnt = rhs->ptr_cnt;
27. rhs.ptr_cnt->cnt++;
28. ptr_cnt->cnt--;
29. if (ptr_cnt->cnt == 0)
30. delete ptr_cnt;
31. return *this;
32. }
33. // 解引用运算符重载函数
34. T &operator*()
35. {
36. return *(ptr_cnt->cnt);
37. }
39. // 析构函数
40. ~SmartPtr()
41. {
42. ptr_cnt->cnt--;
43. if (ptr_cnt->cnt == 0)
44. delete ptr_cnt;
45. else
46. cout << "还有" << ptr_cnt->cnt << "个指针指向基础对象" << endl;
47. }
48. };
`
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
(3) 使用与测试
`
1. #include<iostream>
3. using namespace std;
6. // 测试函数
7. void test()
8. {
9. int *p = new int(42);
10. {
11. SmartPtr<int> sptr1(p); // 出了作用域,计数器减1
12. {
13. SmartPtr<int> sptr2(sptr1); // 出了作用域,计数器减1
14. {
15. SmartPtr<int> sptr3(sptr1); // 出了作用域,计数器减1
16. }
17. }
18. }
19. cout << *p << endl; // 动态分配的对象已被释放,故输出垃圾值
20. }
22. // 主函数
23. int main()
24. {
25. test();
26. return 0;
27. }
运行结果如下:
如图所示,在离开大括号后,共享基础对象的指针计数从3->2->1->0变换,最后计数为0时,指针p指向的动态分配对象被delete,此时使用\*p已经获取不到原来的值,故而产生垃圾值。