智能指针

当我们在C++申请内存的时候,使用下面的语句:

int *p = new int;

这行代码的意思就是在堆上申请了一块内存,并用指针p去指向他:
在这里插入图片描述
由此,引出来堆和栈的区别:

  • 栈:系统开辟、系统释放
  • 堆:人为开辟、人为释放
    在这里,就存在了人为的问题,如果因为忘记或者是代码跳跃的原因跳过了delete,这个时候就会发生内存泄漏。
    如果设计出一种人为开辟,系统释放的机制,就可以解决内存泄漏的问题,即自主的内存回收机制–智能指针
    在JAVA中,只有new关键字,没有delete关键字,因为在JAVA中,实现了自主的内存回收机制–垃圾回收机制:在后台开启了一个守护进程,目的是监控变量的生存周期。

智能指针的实现:

  • 以前一块内存申请出来是交给一个栈上的变量来管理,现在智能指针是交给栈上的一个对象来管理,那么在对象里面,就要存在一个指针指向堆内存。等到对象的周期到了之后,就会自动的释放里面指针指向的堆内存。
  • 因为不知道要用智能指针管理怎样的堆内存,所以应该设计成模板

在C++中,智能指针有四种:
C++98:auto_ptr
C++11:unique_ptr、shared_ptr、weak_ptr

一、auto_ptr
特性:所有权唯一,即指向一块内存的指针数目唯一

  • 当我们使用:
	Auto_ptr<int> ap1(new int);
	Auto_ptr<int> ap2 = ap1;

图解如下:
在这里插入图片描述
在释放的时候,ap2对象先释放,把指向的空间和自身的对象都释放了;等到ap1释放的释放,释放了同一块内存,系统就会崩溃。
解决方法:新的智能指针获取该堆内所有权时,就会取消掉旧智能指针的所有权

  • 第一种情况类似于浅拷贝,还有一种情况是=运算符的重载
    在这里插入图片描述
    执行后的结果是:
    在这里插入图片描述
    此时,ap1就会有内存泄漏产生,所以也要写一个=运算符的重载。
  • 并且还要有*和->的重载,如果对指针进行操作的话,解引用和指向运算符都是有意义的,所以对于对象也要有

实现:

template<typename T>
class Auto_ptr
{
public:
	Auto_ptr(T* ptr):
		mptr(ptr)
	{}

	Auto_ptr(Auto_ptr<T>& rhs)
	{
		mptr = rhs.Release();
	}

	Auto_ptr<T>& operator=(Auto_ptr<T>& rhs)
	{
		if(this != &rhs)
		{
			delete mptr;
			rhs.Release();
		}
		return *this;
	}

	~Auto_ptr()
	{
		delete mptr;
	}

	T& operator*()
	{
		return *mptr;
	}

	T& operator->()
	{
		return mptr;
	}
private:
	T* Release()
	{
		T* ptr = mptr;
		mptr = NULL;
		return ptr;
	}
	T* mptr;
};

缺点:在发生权限转移的时候,由于释放了指针,所以不能共享,导致了其他智能指针的提前实效

  • 普通指针
	int *p = new int;
	int *q = p;
	*p = 20;

这样写是没有问题的,但是如果是智能指针呢??

  • 智能指针
	Auto_ptr<int> ap1 = new int;
	Auto_ptr<int> ap2 = ap1;
	*ap2 = 20;
	*ap1 = 10;

会在第四行出现错误,运行前两行代码后:
在这里插入图片描述
ap2是有空间指向的,但是对于ap1来说,就会造成系统崩溃。

二、带有标志位的智能指针(不属于C++标准里面的)

  • 因为auto_ptr不能共享,所以此智能指针解决的正是共享的问题,即允许多个智能指针指向同一堆内存。
  • 但是,在多个指针中只有一个是有内存的释放权限,设置为布尔类型。

权限的转移也很简单:

  • 无论旧指针是否有释放权限,都将其权限赋给新指针,并且自己的权限置为false
template<typename T>
class Smart_ptr
{
public:
	Smart_ptr(T* ptr)
		:mptr(ptr)
	{}

	Smart_ptr(const Smart_ptr<T>& rhs)
	{
		mptr = rhs.mptr;
		flag = rhs.flag;
		rhs.flag = false;
	}

	Smart_ptr<T>& operator=(const Smart_ptr<T>& rhs)
	{
		if(this == &rhs)
		{
			if(flag)
			{
				delete mptr;
			}
			mptr = rhs.mptr;
			flag = rhs.flag;
			rhs.flag = false;
		}
		return *this;
	}

	~Smart_ptr()
	{
		if(flag)
		{
			delete mptr;
		}
		mptr = NULL;
	}
	T& operator*()
	{
		return *mptr;
	}
	T& operator->()
	{
		return mptr;
	}
private:
	T* mptr;
	bool flag;
};

此时,auto_ptr的问题就不存在了:

	Auto_ptr<int> ap1 = new int;
	Auto_ptr<int> ap2 = ap1;
	*ap2 = 20;
	*ap1 = 10;

但是,以下这种情况就会出现问题:

void GetSmart_Ptr(Smart_ptr<int> sp)
{}

int main()
{
	Smart_ptr<int> sp1(new int);
	Smart_ptr<int> sp2 = sp1;
	GetSmart_Ptr(sp2);
	*sp1 = 20;
	return 0;
}
  1. 首先sp1的flag = true;
  2. sp1的flag= false;sp2的flag = true;
  3. 函数形参中的变量的flag = true;等到函数退出后,所有的对象的flag都是false了,对空间都没有释放权限了。就会造成内存泄漏。

三、unique_ptr
在前两种智能指针中,都会存在智能指针提前失效的问题,关键点就在于发生了拷贝或者赋值导致的权限转移,从而提前失效。
禁止权限转移的智能指针–unique_ptr

template<typename T>
class Unique_ptr
{
public:
	Unique_ptr(T* ptr)
		:mptr(ptr)
	{}

	~Unique_ptr()
	{
		delete mptr;
	}

	T& operator*()
	{
		return *mptr;
	}

	T& operator->()
	{
		return mptr;
	}
private:
	Unique_ptr(const Unique_ptr<T>&);
	Unique_ptr<T>& operator=(const Unique_ptr<T>&);
	T* mptr;
};
  • 虽然设计思想是所有权唯一,即一个智能指针指向一块区域;但是没有办法控制外部人员的使用行为:
	int *p = new int;
	Unique_ptr<int> sp1(p);
	Unique_ptr<int> sp2(p);
	Unique_ptr<int> sp3(p);

在这里插入图片描述
如果运行上述代码,会发生图示情况,所以在释放的时候就会发生错误。

由此,提出来shared_ptr智能指针

四、shared_ptr(带引用计数的智能指针)
首先要满足一下两个条件:

  1. 共享
  2. 最后一个智能指针对象销毁

怎么判断是否是最后一个对象销毁?
计数器 记录该堆内存有多少个对象指向
计数器的数据结构:
在这里插入图片描述

  • 如果再有指针指向同一块内存,引用计数+1就好
  • 如果引用计数减为0,那么这个指针就要负责对该内存进行释放
class Mem_Ref
{
public:
	Mem_Ref()
	{
		size = 0;
	}

	void addRef(void* ptr)
	{
		if(NULL == ptr)
		{
			return;
		}
		int index = find(ptr);
		if(index < 0)
		{
			Node tmp(ptr,1);
			node[size++] = tmp;
		}
		else
		{
			node[index].ref++;
		}
	}

	void delRef(void* ptr)
	{
		if(NULL == ptr)
		{
			return;
		}
		int index = find(ptr);
		if(index < 0)//找不到
		{
			throw std::exception("ptr is error!");
		}
		else
		{
			if(node[index].ref > 0)
			{
				node[index].ref--;
			}
		}
	}

	int getRef(void* ptr)
	{
		if(NULL == ptr)
		{
			return -1;
		}
		int index = find(ptr);
		if(index < 0)
			return -1;
		else
		{
			return node[index].ref;
		}
	}
private:
	int find(void* ptr)
	{
		for(int i = 0;i < size;i++)
		{
			if(node[i].addr == ptr)
			{
				return i;
			}
		}
		return -1;
	}
	struct Node
	{
		Node(void* add = NULL,int count = 0)
		{
			addr = add;
			ref = count;
		}
		void* addr;
		int ref;
	};
	Node node[10];
	int size;
};

template<typename T>
class Shared_Ptr
{
public:
	Shared_Ptr(T* ptr = NULL)
		:mptr(ptr)
	{
		mr.addRef(mptr);
	}

	Shared_Ptr(Shared_Ptr<T>& rhs)
	{
		mptr = rhs.mptr;
		mr.addRef(mptr);
	}

	Shared_Ptr<T>& operator=(Shared_Ptr<T>& rhs)
	{
		if(this != &rhs)
		{
			mr.delRef(mptr);
			if(mr.getRef(mptr) == 0)
			{
				delete mptr;
			}
			mptr = rhs.mptr;
			mr.addRef(mptr);
		}
		return *this;
	}

	~Shared_Ptr()
	{
		mr.delRef(mptr);
		if(mr.getRef(mptr) == 0)
		{
			delete mptr;
		}
		mptr = NULL;
	}

	T& operator*()
	{
		return *mptr;
	}

	T* operator->()
	{
		return mptr;
	}

	T* getPtr()
	{
		return mptr;
	}
private:
	T* mptr;
	static Mem_Ref mr;
};
template<typename T>
Mem_Ref Shared_Ptr<T>::mr;
  • 但是当我们对类互相引用的时候就会出现错误:
class B;
class A
{
public:
	A()
	{
		std::cout << "A::A()" << std::endl;
	}

	~A()
	{
		std::cout << "A::~A()" << std::endl;
	}
public:
	Shared_Ptr<B> spa;
};

class B
{
public:
	B()
	{
		std::cout << "B::B()" << std::endl;
	}

	~B()
	{
		std::cout << "B::~B()" << std::endl;
	}
public:
	Shared_Ptr<A> spb;
};
int main()
{
	Shared_Ptr<A> pa(new A());
	Shared_Ptr<B> pb(new B());
	pa->spa = pb;
	pb->spb = pa;
	return 0;
}

会造成内存泄漏
Shared_Ptr pa(new A())
在这里插入图片描述
Shared_Ptr pb(new B())
在这里插入图片描述
pa->spa = pb;pb->spb = pa;

在这里插入图片描述
当退出后:
在这里插入图片描述
现在变成了:
在这里插入图片描述
也就是说spa、spb没有释放,所以此时运行后会有A() B(),但是没有~A() ~B()

五、weak_ptr
弱智能指针的出现就是为了解决强智能指针相互引用的问题的。

  1. 不添加引用计数,单纯管理堆内存
  2. 结合强智能指针一起使用,不能单独使用(析构函数不释放内存)

如果是类相互引用,那么应该是下面这样写,并且搭配上强智能指针:

template<typename T>
class Weak_ptr
{
public:
	Weak_ptr(T* ptr = NULL)
		:mptr(ptr)
	{}

	Weak_ptr(Weak_ptr<T>& rhs)
		:mptr(rhs.mptr)
	{}

	Weak_ptr<T>& operator=(Weak_ptr<T>& rhs)
	{
		if(this != &rhs)
		{
			mptr = rhs.mptr;
		}
		return *this;
	}

	Weak_ptr<T>& operator=(Shared_Ptr<T>& rhs)
	{
		mptr = rhs.getPtr();
		return *this;
	}

	~Weak_ptr(){}

	T& operator*()
	{
		return *mptr;
	}

	T& operator->()
	{
		return mptr;
	}
	
	T* getPtr()
	{
		return mptr;
	}private:
	T* mptr;
};

class B;
class A
{
public:
	A()
	{
		std::cout << "A::A()" << std::endl;
	}
	~A()
	{
		std::cout << "A::~A()" << std::endl;
	}
public:
	Weak_ptr<B> spa;
};

class B
{
public:
	B()
	{
		std::cout << "B::B()" << std::endl;
	}
	~B()
	{
		std::cout << "B::~B()" << std::endl;
	}
public:
	Weak_ptr<A> spb;
};

输出:

A::A()
B::B()
B::~B()
A::~A()
请按任意键继续. . .

可见解决了内存泄漏的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值