文章目录
- 智能指针
- 为什么需要智能指针?
- RAII
- auto_ptr,管理权转移
- unique_ptr,不允许拷贝
- shared_ptr,引用计数
- 循环引用
- weak_ptr,不是RAII智能指针,专门解决循环引用问题
智能指针
为什么需要智能指针?
在C++中new一个对象以后,需要手动的释放这块空间,这样会触发很多情况,导致不能被释放。
int mian()
{pair<string,string>* p1 = new pair<string,string>;func();delete p1;return 0;
}
假如这里的func()函数抛出异常,那么delete不能够执行到位,会造成内存泄漏。
RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:不需要显式地释放资源。采用这种方式,对象所需的资源在其生命期内始终保持有效。
第一个版本的智能指针
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
template <class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};int main()
{SmartPtr<string> p1(new string("xxxxx"));SmartPtr<string> p2(new string("yyyyyy"));p1 = p2;return 0;
}
这个情况下,p1的指针指向p2的空间,造成p1的空间泄露,p2指向的空间不仅有p2指向,现在还有p1指向,会导致free两次。
auto_ptr,管理权转移
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<memory>
using namespace std;
class A
{
public: A(int a = 1):_a(a){cout << "A(int a = 1)" << endl;}~A(){cout << "~A()" << endl;}
//private:int _a;
};
int main()
{auto_ptr<A> p1(new A(1));auto_ptr<A> p2(new A(2));//auto_ptr 会将管理权转移,拷贝时,会把拷贝对象的资源管理权转移给被拷贝的对象//导致被拷贝对象悬空,访问就会出现问题auto_ptr<A> p3 = p1; //这里的p1就悬空了//崩溃p1->_a++;p3->_a++;return 0;
}
unique_ptr,不允许拷贝
template<class T>class unique_ptr{public:unique_ptr(T* ptr):_ptr(ptr){}//防止拷贝unique_ptr(unique_ptr<T>& ptr) = delete;unique_ptr<T> operator=(unique_ptr<T>& ptr) = delete;T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
shared_ptr,引用计数
#include <iostream>
using namespace std;template<class T>
class shared_ptr
{
public:shared_ptr(T* ptr): _ptr(ptr), _pcount(new int(1)){}~shared_ptr(){if (--(*_pcount) == 0){std::cout << "delete " << _ptr << std::endl;delete _ptr;delete _pcount;}}shared_ptr(const shared_ptr<T>& ptr): _ptr(ptr._ptr)//同一个类的实例可以互相访问私有变量, _pcount(ptr._pcount){(*_pcount)++;}shared_ptr<T>& operator=(const shared_ptr<T>& ptr){if (this == &ptr){return *this;}if (--(*_pcount) == 0){delete _ptr;delete _pcount;}_ptr = ptr._ptr;_pcount = ptr._pcount;++(*_pcount);return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get(){return _ptr;}private:T* _ptr;int* _pcount;
};
循环引用
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<memory>
using namespace std;
class A
{
public: A(int a = 1):_a(a){cout << "A(int a = 1)" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};
struct Node
{A _val;shared_ptr<Node> prev;shared_ptr<Node> next;
};
int main()
{shared_ptr<Node> sp1(new Node);shared_ptr<Node> sp2(new Node);sp1->next = sp2;sp2->prev = sp1;return 0;
}
-
Node
结构体包含了两个sh ared_ptr<Node>
成员:prev
和next
,它们分别指向前一个和后一个节点。 -
shared_ptr
是一种智能指针,使用引用计数来管理对象的生命周期。当一个新的shared_ptr
指向某个对象时,该对象的引用计数增加;当一个shared_ptr
被销毁或指向其他对象时,引用计数减少。 -
sp1
和sp2
分别是两个shared_ptr<Node>
对象,指向两个不同的Node
实例。 -
然后,
sp1->next
被设置为sp2
,sp2->prev
被设置为sp1
,这就导致了sp1
和sp2
互相引用。
循环引用的产生
-
sp1
引用sp2
:- 当
sp1->next = sp2
执行后,sp1
中的next
指向了sp2
,这意味着sp2
的引用计数增加了 1。
- 当
-
sp2
引用sp1
:- 当
sp2->prev = sp1
执行后,sp2
中的prev
指向了sp1
,这意味着sp1
的引用计数也增加了 1。
- 当
由于 sp1
和 sp2
互相引用对方,形成了一个循环引用。
循环引用的后果
当 main
函数结束时,sp1
和 sp2
都应该被销毁,然而,由于它们互相引用,两个 shared_ptr
的引用计数都不会降到 0,因此内存不会被释放。
sp1
持有一个指向sp2
的shared_ptr
,这使得sp2
的引用计数永远不会降到 0。- 同样,
sp2
持有一个指向sp1
的shared_ptr
,使得sp1
的引用计数也永远不会降到 0。
这种情况下,即使 sp1
和 sp2
超出了作用域并且 main
函数结束了,它们所指向的 Node
对象依然无法被销毁,导致内存泄漏。
weak_ptr,不是RAII智能指针,专门解决循环引用问题
原理:不增加引用计数,不参与资源的释放管理,可以访问资源。
template<class T>class weak_ptr{public:weak_ptr(T* ptr = nullptr):_ptr(ptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};