当前位置: 首页 > news >正文

C++----智能指针

智能指针出现的原因

智能指针是专门用来解决内存泄漏问题的:

int div()
{
 int a, b;
 cin >> a >> b;
 if (b == 0)
 throw invalid_argument("除0错误");
 return a / b;
}
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
 int* p1 = new int;
 int* p2 = new int;
 cout << div() << endl;
 delete p1;
 delete p2;
}
int main()
{
 try
 {
 Func();
 }
 catch (exception& e)
 {
 cout << e.what() << endl;
 }
 return 0;
}

如果在p1抛异常,那么p1内存不会被释放;在p2抛异常,那么p1和p2内存不会被释放;div抛异常,那么p1和p2不会被释放。这就造成了内存泄漏,而智能指针是专门为解决内存泄漏问题出现的。

内存泄漏

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内 存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死。

堆内存泄漏(Heap leak) 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一 块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分 内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

系统资源泄漏 指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放 掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄 漏检测工具(昂贵且不靠谱)

智能指针

 1.RAII的思想和原理

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。

具体实现方式就是在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:不需要显式地释放资源;采用这种方式,对象所需的资源在其生命期内始终保持有效。

对于智能指针来说,在模板类中还需要重载*,->运算符让它看起来像"指针"一样。

总结一下,智能指针的实现原理就是:

1. RAII特性

2. 重载operator*和opertaor->,具有像指针一样的行为。

但是智能指针只需要注意这些就可以了吗?当然不是,还需要注意拷贝问题!这里的拷贝都会浅拷贝,所以会涉及资源管理的问题,接下来看看库里面的智能指针都是怎么解决这个问题的! 

2.std::auto_ptr 

auto_ptr指针在C++98中就被提出。它解决关于拷贝权限的问题很简单,就是将原指针悬空,管理权转移的思想,原指针不具备指向资源的能力了。简单模拟实现一下

	template<class T>
	class auto_ptr
	{
	public:
		//ARII
		auto_ptr(T* _ptr)
			:ptr(_ptr)
		{

		}

		//转移权限
		auto_ptr(auto_ptr<T>& t)
			:ptr(t.ptr)
		{
			t.ptr = nullptr;
		}


		~auto_ptr()
		{
			if (ptr)
			{
				cout << "delete auto" << endl;
				delete ptr;
				ptr = nullptr;
			}
		}

		//像指针一样
		T operator*()
		{
			return *ptr;
		}

		T* operator->()  //ap.operator->()
		{
			return ptr;
		}

		T* get()
		{
			return ptr;
		}
	public:
		T* ptr;
	};

这种解决方法并不好,所以后来在很多企业中都禁止使用auto_ptr。 

3.unique_ptr(scoped_ptr)

C++11提出了unique_ptr,但是早在C++11之前,boost库中就已经存在了scoped_ptr,是一样的,unique_ptr就是scoped_ptr。unique_ptr直接禁止拷贝!

	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* _ptr)
			:ptr(_ptr)
		{
		}

		~unique_ptr()
		{
			if (ptr)
			{
				cout << "delete auto" << endl;
				delete ptr;
				ptr = nullptr;
			}
		}

		//像指针一样
		T operator*()
		{
			return *ptr;
		}

		T* operator->()  //ap.operator->()
		{
			return ptr;
		}

		T* get()
		{
			return ptr;
		}

		//不让拷贝构造和赋值!防止还有人进行拷贝和赋值
		unique_ptr(const unique_ptr<T>& p) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& p) = delete;
	public:
		T* ptr;
	};

4.shared_ptr和weak_ptr

C++11还有shared_ptr,它是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共 享;

2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一;

3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;

4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对 象就成野指针了。

	template<class T>
	class shared_ptr
	{
	public:

		void Release()
		{
			if (ptr && --(*pcount)==0)
			{
				cout << "delete shared" << endl;

				delete ptr;
				ptr = nullptr;
				delete pcount;
				pcount = nullptr;
			}
		}

		shared_ptr(T* _ptr = nullptr)
			:ptr(_ptr),
			pcount(new int(1))
		{}

		shared_ptr(const shared_ptr<T>& t)
			:ptr(t.ptr),
			pcount(t.pcount)
		{
			(*pcount)++;
		}

		//shared_ptr<T>& operator=(shared_ptr<T>& t)
		//{//避免 T* p1= new T,T* p2 = new T; shared_ptr<T> sp1(p1); shared_ptr<T> sp2(p2);sp1 = sp2;如果不释放sp1,那么p1就不会被释放。
		//	Release();

		//	ptr = t.ptr;
		//	pcount = t.pcount;
		//	(*pcount)++;
		//}
		shared_ptr<T>& operator=(shared_ptr<T>& t)
		{//避免 T* p1= new T,T* p2 = new T; shared_ptr<T> sp1(p1); shared_ptr<T> sp2(p2);sp1 = sp2;如果不释放sp1,那么p1就不会被释放。
			if (ptr != t.ptr)
			{
				Release();

				ptr = t.ptr;
				pcount = t.pcount;
				(*pcount)++;
			}

			return *this;
		}

		~shared_ptr()
		{
			Release();
		}

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

		T operator*()
		{
			return *ptr;
		}

		T* get() const
		{
			return ptr;
		}

		int usecount()
		{
			return *pcount;
		}
	public:
		T* ptr;
		int* pcount;
	};

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr(T* _ptr)
			:ptr(_ptr)
		{}

		weak_ptr(shared_ptr<T>& t)
			:ptr(t.ptr)
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (ptr != sp.get())
			{
				ptr = sp.get();
			}

			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *ptr;
		}

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

	public:
		T* ptr;
	};

但是说明这样是不是就完美了?并不是,在某些情况下会出现bug。

struct ListNode
{
	wzz::shared_ptr<ListNode> _next = nullptr;
	wzz::shared_ptr<ListNode> _prev = nullptr;

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

};

int main()
{
	wzz::shared_ptr<ListNode> p1(new ListNode);
	wzz::shared_ptr<ListNode> p2(new ListNode);

	//循环引用
	p1->_next = p2;
	p2->_prev = p1;

}


//在这个时候就不会调用~ListNode和~shared_ptr了!

解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了

原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和 _prev不会增加node1和node2的引用计数。

struct ListNode
{
	wzz::weak_ptr<ListNode> _next = nullptr;
	wzz::weak_ptr<ListNode> _prev = nullptr;

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

};

int main()
{
	wzz::shared_ptr<ListNode> p1(new ListNode);
	wzz::shared_ptr<ListNode> p2(new ListNode);

	//循环引用
	p1->_next = p2;
	p2->_prev = p1;

}

//weak_ptr的唯一作用就是解决shared_ptr中存在的循环引用问题.

定制删除器 

在我们写的,包括库里面的智能指针的默认清理资源方式都是delete ptr的形式,如果是这些申请资源的方式呢?

int main()
{
    wzz::unique_ptr<int> p1((int*)malloc(40));
    wzz::unique_ptr<int> p2(new int[10]);
    wzz::unique_ptr<int> p3((int*)malloc(40));
    wzz::unique_ptr<FILE> p4((FILE*)fopen("test.cpp", "r"));
}

 需要针对不同的申请方式进行资源清理,这里就需要定制的清除器了,实际上就是仿函数。

 unique_ptr的默认清理方式就是delete的形式。

	template<class T>
	struct default_delete
	{
		void operator()(T* ptr)
		{
			delete ptr;
			ptr = nullptr;
		}
	};

	template<class T, class D = default_delete<T>>
	class unique_ptr
	{
	public:
		unique_ptr(T* _ptr)
			:ptr(_ptr)
		{
		}

		~unique_ptr()
		{
			if (ptr)
			{
				cout << "delete auto" << endl;
				D del;
				del(ptr);
			}
		}

		//像指针一样
		T operator*()
		{
			return *ptr;
		}

		T* operator->()  //ap.operator->()
		{
			return ptr;
		}

		T* get()
		{
			return ptr;
		}

		//不让拷贝构造和赋值!
		unique_ptr(const unique_ptr<T>& p) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& p) = delete;
	public:
		T* ptr;
	};




template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

template<class T>
struct Free
{
	void operator()(T* ptr)
	{
		free(ptr);
	}
};

struct Fclose
{
	void operator()(FILE* ptr)
	{
		fclose(ptr);
	}
};
int main()
{
	wzz::unique_ptr<int> p1((int*)malloc(40));
	wzz::unique_ptr<int> p2(new int[10]);
	wzz::unique_ptr<int> p3((int*)malloc(40));
	wzz::unique_ptr<FILE> p4((FILE*)fopen("test.cpp", "r"));
}

对于shared_ptr的删除方式和unique_ptr是不太一样的,他支持构造函数传参:

其内部实现比较复杂。 

总结

智能指针就是RAII的思想,像指针一样去使用,考虑拷贝问题。

auto_ptr是权限转移,比较鸡肋,不需要拷贝有更新的unique,拷贝的话又有shared;

unique_ptr不支持拷贝,在不需要拷贝的情况下比较舒服;

shared_ptr比较靠谱,但是会有"循环引用"的bug出现,用weak_ptr来解决。

相关文章:

  • SpringMVC处理Ajax请求及处理和响应json格式的数据
  • 论文复现(一)
  • 龙芯+复旦微FPGA全国产VPX高速数据采集卡解决方案
  • 前端blob数据
  • Jenkins+ant+mysql 自动化构建脚本文件输出日志
  • Unity 渲染YUV数据 ---- 以Unity渲染Android Camera数据为例子
  • 高德骨子里还是个「理工男」
  • Vue指令学习 | 零基础入门
  • promise执行顺序面试题令我头秃,你能作对几道
  • 基于springboot的图书管理系统设计与实现
  • const char *p,char const *p和char *const p区别
  • disruptor (史上最全之1):伪共享原理性能对比实战
  • 网络安全防御体系建设-防守实例
  • 基于Java+SpringBoot+Thymeleaf+Mysql在线外卖点餐系统设计与实现
  • 手把手带你免费申请《软件著作权》 超详细计算机软件著作权申请教程 文末送模板
  • @jsonView过滤属性
  • 002-读书笔记-JavaScript高级程序设计 在HTML中使用JavaScript
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • DOM的那些事
  • ECMAScript6(0):ES6简明参考手册
  • Linux Process Manage
  • Meteor的表单提交:Form
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • orm2 中文文档 3.1 模型属性
  • Sass 快速入门教程
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • 从 Android Sample ApiDemos 中学习 android.animation API 的用法
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 干货 | 以太坊Mist负责人教你建立无服务器应用
  • 欢迎参加第二届中国游戏开发者大会
  • 推荐一个React的管理后台框架
  • 再谈express与koa的对比
  • 最近的计划
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • $(function(){})与(function($){....})(jQuery)的区别
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (52)只出现一次的数字III
  • (9)STL算法之逆转旋转
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (ibm)Java 语言的 XPath API
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (篇九)MySQL常用内置函数
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)树状数组
  • (转)详解PHP处理密码的几种方式
  • .java 9 找不到符号_java找不到符号
  • .NET BackgroundWorker
  • .Net Web项目创建比较不错的参考文章
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调
  • .NET 中 GetProcess 相关方法的性能
  • .NET命名规范和开发约定
  • @RequestBody的使用