智能指针

C++中没有垃圾回收器,所以对于我们自己申请的内存必须自己来释放,而且要正确释放。但是当程序代码较为庞杂的时候,对于指针的管理开始变得困难起来,申请多个指针,不免会有疏漏而忘记释放,程序异常的抛出导致的跳转而遗漏等等,那么,如何高效的管理指针就成为当前一个棘手的问题。

为解决这个问题,我们的前辈得出了智能指针的这个方法,智能指针到底怎样实现智能呢?下面我们一起来看看下面这段代码

#include<malloc.h>
class SmartPtr
{
public:
	SmartPtr(int *ptr=nullptr):_ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	int *_ptr;
};
int main()
{
	int *temp = (int*)malloc(sizeof(int));
	SmartPtr sp(temp);
	return 0;
}

智能指针其本质就是利用类对象的生命周期来控制程序资源,当程序返回时,类对象自动调用类的析构函数,来达到自动释放空间,这样的行为看起来是smart的,因此称它为智能指针。

但SmartPtr这样还没有完全具有指针的行为(解引用,指向)。对于智能指针有了一个基本的认知,库中的智能指针的根本思想和上面的SmartPtr一致,接下来,我们来模拟实现库中的AutoPtr

#include<malloc.h>
#include<iostream>
using namespace std;
class Person
{
public:
	Person(int age=5) :_age(age) {}
	int _age;
};
class AutoPtr
{
public:
	AutoPtr(Person *ptr=nullptr):_ptr(ptr)
	{}
	AutoPtr(AutoPtr& sp)
		:_ptr(sp._ptr)
	{
	   sp._ptr = NULL;
	}
	AutoPtr operator=(AutoPtr& sp)
	{
		if (this != &sp)//判断是否是自己给自己赋值
		{
			if (_ptr)//释放当前对象资源
				delete _ptr;

			_ptr = sp._ptr;//转移资源到被赋值对象
			sp._ptr = NULL;
		}	
	}
	~AutoPtr()
	{
		if (_ptr)
			delete _ptr;
	}

	Person& operator*()
	{
		return *_ptr;
	}
	Person* operator->()
	{
		return _ptr;
	}
private:
	Person *_ptr;
};

int main()
{
	AutoPtr ptr(new Person);
	AutoPtr pcr(ptr);
	ptr->_age = 10;
	return 0;
}

当管理指针的对象被拷贝或者赋值时,会导致一份资源被多个对象引用时,在释放的时候会多次调用析构函数,导致一份资源被多次释放,这是不合理的。AutoPtr在处理拷贝和赋值问题上使用了资源转移的思想来解决这个问题,但以这样的方式处理问题又带来了新的问题,当对象的资源被转移,对象的指针被置空,但当程序未返回时,这个空指针一直存在,这可能会导致误用空指针,所以AutoPtr是不安全的,为了解决这个问题,我们又有了UniquePtr。

UniquePtr的方式很直接有效,那就是禁止掉拷贝和赋值这两种操作,下面看代码:

#include<malloc.h>
#include<iostream>
using namespace std;
class Person
{
public:
	Person(int age = 5) :_age(age) {}
	int _age;
};
class UniquePtr
{
public:
	UniquePtr(Person *ptr = nullptr) :_ptr(ptr)
	{}
	~UniquePtr()
	{
		if (_ptr)
			delete _ptr;
	}

	Person& operator*()
	{
		return *_ptr;
	}
	Person* operator->()
	{
		return _ptr;
	}
private:
	//C++98
	UniquePtr(UniquePtr const &up);
	UniquePtr& operator = (UniquePtr const &up);
		//C=+11
	UniquePtr(UniquePtr const &up) = delete;
	UniquePtr& operator = (UniquePtr const &up) = delete;
private:
	Person *_ptr;
};

这种方式是不太合理的,一个类对象不能够进行拷贝和赋值,极大的降低了灵活性,当我们需要这种操作时,被禁止掉是不科学的,所以在库中也提供了可以进行拷贝和赋值操作的SharePtr,为了简单的了解原理,下面我们模拟实现一下:

#include<iostream>
#include<thread>
#include<mutex>

using namespace std;

template<class T>

class SharedPtr
{
public:
	SharedPtr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pCount(new int(1))
		, _pMutex(new mutex)
	{
		//如果_ptr是一个空指针则将引用计数置0
		if (_ptr==nullptr)
			*_pCount = 0;
	}
	SharedPtr(const SharedPtr<T>& sp)
		:_ptr(sp._ptr)
		,_pCount(sp._pCount)
		,_pMutex(sp._pMutex)
	{
		//如果_ptr不是一个空指针,则对引用计数+1
		if (_ptr)
			AddCount();
	}
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		//除去本身赋值
		if (this != &sp)
		{
			Release();

			_ptr = sp._ptr;
			_pCount = sp._pCount;
			_pMutex = sp._pMutex;

			AddCount();
		}
		return *this;
	}
	int AddCount()
	{
		//加锁保证操作原子性
		_pMutex->lock();
		++(*_pCount);
		_pMutex->unlock();
		
		return *_pCount;
	}
	int SubCount()
	{
		_pMutex->lock();
		--(*_pCount);
		_pMutex->unlock();
		
		return *_pCount;
	}
	int GetCount() { return *_pCount; }
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
	~SharedPtr() { Release(); }

private:
	void Release()
	{
		if (_ptr&&SubCount() == 0)
		{
			delete _ptr;
			delete _pCount;
		}
	}
private:
	T* _ptr;
	int *_pCount;
	mutex* _pMutex;
};


int main()
{
	SharedPtr<int>temp;
	cout << temp.GetCount() << endl;
	SharedPtr<int>sp1(new int(6));
	cout << sp1.GetCount() << endl;
	SharedPtr<int>sp2(sp1);
	cout << sp1.GetCount() << endl;
	cout << sp2.GetCount() << endl;
	SharedPtr<int>sp3(new int(10));
	sp1 = sp3;
	cout << sp1.GetCount() << endl;
	cout << sp2.GetCount() << endl;
	cout << sp3.GetCount() << endl;
	sp2 = sp3;
	cout << sp1.GetCount() << endl;
	cout << sp2.GetCount() << endl;
	cout << sp3.GetCount() << endl;
	return 0;
}

PS:以上的三种智能指针包含于头文件<memory>

SharePtr看起来似乎可以达到我们几百年使用要求,但它还存在一个致命的问题,循环引用导致的资源泄露:

#include<iostream>
#include<memory>
using namespace std;

struct ListNode {
	int _data;    
	shared_ptr<ListNode> _prev; 
	shared_ptr<ListNode> _next;

	~ListNode() { cout << "~ListNode()" << endl; }
};

int main() 
{
	shared_ptr<ListNode> node1(new ListNode);  
	shared_ptr<ListNode> node2(new ListNode);   
	cout << node1.use_count() << endl;  
	cout << node2.use_count() << endl;

	node1->_next = node2; 
	node2->_prev = node1;


	cout << node1.use_count() << endl; 
	cout << node2.use_count() << endl;

	return 0;
}

node1和node2析构,引用计数都减为1;

node1的next引用node2,next析构了,node2才能析构;

node2的prev引用node1,prev析构了,node1才能析构;

现在你会发现node1和node2形成了一个相互制约的局面,node1管理的next1,但node1想要析构必须听prev的,而prev由node2管理,而node2能否析构又由node1管理的next说了算,它们之间形成了一个闭环,而且这两个对象也无法再被访问到,如果没有针对这种情况的处理办法,它们将一致僵持下去,谁也不能够释放资源,导致资源泄露,这种情况和产生死锁中的环路等待非常类似,但在C++中,这种情况并不叫做死锁,这种现象被称为循环引用。

强引用和弱引用?

强引用是它所引用的对象存在时,这个引用也存在,share_ptr就是强引用;相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它自身存在时的一个引用

为了解决循环引用的问题,我们引入了weak_ptr,原理不再深究,掌握使用方法即可:

weak_ptr必须是由share_ptr或者另一个weak_ptr转换而来

对于上面的循环引用,只要将一方的强引用修改为弱引用即可打破循环引用:

struct ListNode {
	int _data;    
	weak_ptr<ListNode> _prev; 
	shared_ptr<ListNode> _next;

	~ListNode() { cout << "~ListNode()" << endl; }
};

关于weak_ptr:

通过weak_ptr可以有效的解除循环引用,但这种方式必须在程序员能预见可能出现循环引用的情况下才能使用,可见使用智能指针并不能完全杜绝内存泄漏问题,因此,对于内存问题我们必须谨慎处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值