智能指针

引言:在C++中,堆内存的管理都是由在栈上开辟的变量名间接控制的。这是因为在堆上开辟的空间是没有名字的,我们无法直接对齐进行控制。所以当释放该堆内存时,也需要我们去人为的释放它。

智能指针的出现就是为了解决上面的问题,当释放一个堆内存时,它将由系统释放,而不是我们人为的释放它。这样就会避免许多内存泄漏的产生。
只能指针总共分为4种:
C++98 auto_ptr
C++11 unique_ptr shared_ptr weak_ptr

auto_ptr

特点:
1.所有权唯一(即一个堆内存只能有一个指针指向,绝对占有)
2.新智能指针获取到所有权时,取消掉旧智能指针的所有权

缺点:
所有权的权限转移,会使得对象提前失效。
完整代码如下:

#include <iostream>
#include <memory>
template <typename T>
class auto_ptr {
public:
	auto_ptr(T* ptr)
		:mptr(ptr)
	{}
	auto_ptr(auto_ptr<T>& rhs)//浅拷贝构造函数
	{
		mptr = rhs.Release();//让新的指针指向内存块,同时让旧的指针指向为空
		/*
		T* ptr = rhs.mptr;
		mptr = NULL;
		mptr=ptr;
		*/
	}
	auto_ptr<T>& operator=(auto_ptr<T>& rhs)//赋值运算符重载函数
	{
		if (this != &rhs)//判断是否自赋值
		{
			delete mptr;
			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 main()
{
	auto_ptr<int> p1 = new int;
	auto_ptr<int> p2 = p1;//此时p1的唯一权已经转移到p2中,此时p1已经失效
	std::cout << "the p1's size is:" << sizeof(p1) << std::endl;
	std::cout << "the p3's size is:" << sizeof(p2) << std::endl;
	return 0;
}

带标志位的智能指针

为了解决上面auto_ptr让指针提前失效的方法,又出现了一种带标志位的指针。它取消了堆内存的唯一权,即一块内存可以有多个指针指向。但是它的释放权此时唯一,也就是说当最后一个指针(拥有释放权的指针一旦释放)执行析构函数,那么指向这块空间的所有指针也将会释放。
基本概念:新智能指针获取到释放权后,取消掉旧智能指针的释放权
代码如下:

#include <iostream>
#include <memory>
using std:: ostream;
template <typename T>
class smartptr
{
public:
	smartptr(T* ptr)
		:mptr(ptr)
	{}
	smartptr(smartptr<T>& rhs)
	{
		mptr = rhs.mptr;
		flag = rhs.flag;
		rhs.flag = false;
	}
	smartptr<T>& operator=(smartptr<T>& rhs)
	{
		if (this != &rhs)
		{
			if (flag)
			{
				delete mptr;
			}
			mptr = rhs.mptr;
			flag = rhs.flag;
			rhs.flag = false;
		}
		return *this;
	}
	~smartptr()
	{
		if (flag)
		{
			delete mptr;
		}
		mptr = NULL;
	}
	T& operator*()
	{
		return *mptr;
	}
	T* operator->()
	{
		return mptr;
	}

private:
	template <typename T>
	friend ostream& operator<<(ostream& os, smartptr<T>& rhs);
	T* mptr;
	bool flag;
};
template <typename T>
ostream& operator<<(ostream& os, smartptr<T> &rhs)
{
	os << rhs.mptr;
	os << rhs.flag;
	return os;
}

int main()
{
	smartptr<int> p1 = new int;
	smartptr<int> p2 = p1;
	std::cout << p1 << std::endl;
	return 0;
}

unique_ptr

这个智能指针将拷贝构造函数和赋值运算符重载函数写在了私有下,相当于禁止了拷贝和赋值的操作,也被成为最简单的智能指针。

shared_ptr

shared_ptr也叫强智能指针,它依赖一个含有引用计数的结构体Node。
结构体Node设计思想:在Node中存放两个变量void* addr和,int ref.其中addr用来存放在堆内存中开辟出来的地址,(因为开辟堆内存的类型不同,所以将它转为一个void* 的内存)
每当有堆内存开辟出来,我们就将内存的地址存放进来,同时将引用计数++。每当调用一次析构函数,就将堆内存所对应引用计数的值–,如果此时引用计数的值为0,那么我们就认为此时没有指针在指向该内存块,此时将内存块销毁以供系统重新分配。
我们可以将每一个Node看作是结构体中的一条数据,而这个结构体中存放着若干条数据用来存放开辟的堆内存的地址。所以我们会将结构体Node放在一个类中,这个类将提供一系列的方法来添加、查找和删除Node。

#include <iostream>
class mem_ref//结构体方法的接口
{
public:
	mem_ref()//默认的构造函数
	{
		size = 0;//将下标置为0
	}
	void addref(void* ptr)//添加一个新节点
	{
		if (ptr == NULL)//如果指针为空(指向的是空)
		{
			return;//不做处理,直接return出去
		}
		int index = find(ptr);//定义一个整形值来结构find的返回值
		if (index < 0)//如果返回值小于0,说明没有找到这个节点,这时需要为结构添加一个新节点
		{
			node[size].addr = ptr;//添加新堆内存地址到新节点中
			node[size].ref = 1;//将新节点的引用计数初始化为1
			size++;//当前下标已经有了地址,将下标往下走一个
		}
		else//如果大于0,说明找到了指针指向所对应的地址
		{
			node[index].ref++;//将引用计数++(此时又多了一个指针指向该堆内存)
		}
	}
	void deleteref(void* ptr)//删除一个指向
	{
		if (ptr == NULL)//如果指针为空(指向的是空)
		{
			return;//不做处理,直接return出去
		}
		int index = find(ptr);//定义一个整形值来结构find的返回值
		if (index < 0)//如果没有找到指针对应的地址,说明在结构体中没有存储该指针的地址
		{
			throw std::exception("ptr is error!");//发出异常
		}
		else
		{
			if (node[index].ref > 0)//返回值大于0表明找到了该内存的位置
			{
				node[index].ref--;//将引用计数--
			}
		}
	}
	int getref(void* ptr)//查找一个地址,并返回指向该地址的个数
	{
		if (ptr == NULL)//指向为空
		{
			return -1;//返回-1
		}
		int index = find(ptr);//定义一个整形值来结构find的返回值
		if (index < 0)//返回值小于0,没有找到
		{
			return -1;//报错
		}
		else
		{
			return node[index].ref;//返回相应的个数
		}
	}
private:
	int find(void* ptr)
	{
		for (int i = 0; i < size; i++)//i小于当前存数个数的下标
		{
			if (node[i].addr = ptr)遍历下标中的堆内存,查看是否相匹配
			{
				return i;
			}
		}
		return -1;
	}
	struct Node
	{
		Node(void* add=NULL, int cnt = 0)//Node的构造函数
			:addr(add), ref(cnt)
		{}
		void* addr;//定义一个void*的指针来指向该内存(不能保证堆内存的类型,所以使用void*)
		int ref;//引用计数
	};
	Node node[10];//申请10个空间存放堆内存和引用个数
	int size;
};
template <typename T>
class shared_ptr
{
public:
	shared_ptr(T* ptr = NULL)
		:mptr(ptr)//构造函数初始化
	{
		mm.addref(mptr);//每初始化一个就调用一次addref()接口
	}
	shared_ptr(shared_ptr<T>& rhs)//拷贝构造函数
	{
		mptr = rhs.mptr;//将mptr指向rhs.mptr
		mm.addref(mptr);//每次指向一个堆内存,该堆内存的引用计数++
	}
	shared_ptr<T>& operator=(shared_ptr<T>& rhs)//赋值运算符的重载函数
	{
		if (this != &rhs)//判断是否自赋值
		{
			mm.deleteref(mptr);//将赋值的引用计数--
			if (mm.getref(mptr) == 0)//如果--后引用计数为0,则删除该堆内存
			{
				delete mptr;
			}
			mptr = rhs.mptr;//rhs.mptr指向mptr
			mm.addref(mptr);//赋值后,多了一个指向来指向该堆内存,调动该addref()函数
		}
		return *this;//如果是自赋值,直接将this指针return出去
	}
	~shared_ptr()
	{
		mm.deleteref(mptr);//调动deleteref删除一个引用计数
		if (mm.getref(mptr) == 0)//判断是否为0
		{
			delete mptr;//如果是,删除该堆内存
		}
		else
		{
			mptr = NULL;//如果不是,说明还有其他的指针指向该堆内存,将该指针的指向置为空
		}
	}
	T* operator->()
	{
		return mptr;
	}
	T& operator*()
	{
		return *mptr;
	}
private:
	T* mptr;
	static mem_ref mm;//定义一个全局变量来调动接口
};
template <typename T>
mem_ref shared_ptr<T>::mm;//mem_ref类型的,在shared_ref作用于下的一个全局静态变量
int main()
{
	int* p = new int;
	shared_ptr<int> sp1(p);
	shared_ptr<int> sp2(p);
	shared_ptr<int> sp3(p);
	return 0;
}

但是shared_ptr智能指针也会存在一些缺陷。当两个对象相互引用时,就会将各自的引用计数都++一下,导致在释放指针的时候,会出现一个指向但是又两个引用计数的局面。

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;
}

假设以上这种情况,在A类中定义了一个B类型的变量spa,又在B类中定义了一个A类型的变量spb。在new(A)中,会出现一个B类型的对象spa,此时也会调动shared_ptr的构造函数调动,而对象pa又指向了对象spa,同理new(B)也是一样:
在这里插入图片描述
为了解决这一问题,人们又设计出了weak_ptr(弱智能指针),这个智能指针专门用于解决强智能指针相互引用的问题
1.不添加引用计数
2.结合强智能指针一起使用,不能单独使用

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值