用户申请堆上的空间,会用一个指针指向将其保存起来,以便于对其进行释放,但往往释放的情况有多种,所以有时会忘记释放,有可能会造成内存泄漏。智能指针的提出是为了解决此类问题,将其被封装在一个类中,在构造的时候创建,在析构的时候释放。因为智能指针都是栈空间上类的对象。所以,当函数(程序)运行结束后,会自动调用其析构函数自动释放。
智能指针实现的基本思想,利用一个对象的生命周期来控制一份资源的管理(RAII)+对->和*.的重载实现指针的功能。
几种常见的智能指针:
-
auto_ptr
以下就是auto_ptr的实现方式:采用资源管理权的转移,将一个指针所指向的空间交给另一个指针。
但这就引起了auto_ptr的缺点:
管理权一直在变,且只有一个指针指向,两个指针不能指向同一块资源就有很多问题。因为当调用拷贝构造和赋值运算符重载的时候,直接将原来ptr所管理的资源扔掉了,转而交给新的ptr。但这并不是拷贝构造和赋值运算符重载的本意。如下图,执行就会引起程序崩溃。
-
unique_ptr
因为auto_ptr的缺陷,就有其他的智能指针了,unique_ptr的改进就是将一份空间的资源独占,在C++98和C++11中分别对其的防拷贝给出了两种方法:C++98中将拷贝构造和赋值运算符重载给为了private,并且只声明了未定义;C++11将两个函数 = delete,所以当调用这两种方法时编译器会报错。以上都是直接很暴力的方法让指针不能被拷贝。
-
shared_ptr&weak_ptr
shared_ptr引入了一个计数的方式,记录下有多少个对象共享一份资源空间。让多个指针可以指向同一块空间,在构造的时候检测若指向的是共同一块空间,引用计数+1,在析构的时候检查计数为1再delete这块空间。如下图:
但引用计数并不是简单的用一个count去维护,因为这个count是多个智能指针对象所共有的,但若是多线程对其进行++或--的操作,会使count同时改变,这时count的值就会“错误”,与我们所想的不一样,最终有可能导致资源未释放或多次释放导致程序崩溃。所以在对count++或--时要加上互斥锁,才能保证线程安全。
显然这样的智能指针就很好的解决了之前管理权的问题,但是仍然存在一个问题:循环引用,会导致资源空间不能完全释放掉,在双向链表中就会引起此类问题:构建一个结构体用shared_ptr指向_pre和_next,创建两个节点,并建立关系,这时就可以看到p1和p2的_Uses都变为了2,但实际上p1和p2并没有引用同一份空间,所以当调用析构函数的时候,p1和p2的_Uses都只减为1,没有释放掉申请的资源,会引起资源泄露。
这是因为,所以只有_next析构了p2才会释放,_pre析构p1才会释放。但是只有p1释放后,_next才会析构,而p1由_pre管理,_pre又是p2的成员,同理,_pre想要析构,等p2释放了才可以,p2想要释放又得等_next析构才可以。 所以就构成了循环引用。这就是shared_ptr的缺点。
为了解决shared_ptr出现的问题,将ListNode中的_pre和_next改为weak_ptr,因为weak_ptr是使用一个共享的资源而不需要管理权。并且weak_ptr要与shared_ptr配合才能使用。
如下图: