C++:智能指针

智能指针

为什么需要有智能指针?

  • 智能指针是一种预防型的内存泄漏的解决方案。智能指针在C++没有垃圾回收器环境下,保证在任何情况下资源都能够进行合理的释放,而不用在所有可能退出的地方都进行是否释放的检测,避免由此引发的资源泄露问题;

原理

RAII:利用对象的生命周期来控制程序资源;将资源使用类的方式进行封装:在对象构造时获取资源,在析构函数中清理资源

  • 不需要显式释放资源;
  • 采用这种方式对象所需的资源在其生命周期内始终保持有效;

智能指针原理

  • RAII(资源可以自动释放)+operator*/operator->(当成指针使用)+解决浅拷贝的问题
  • 浅拷贝的后果:多个对象共享同一份资源,在销毁对象时,同一份资源被释放多次导致代码崩溃

C++98-auto_ptr:

  • 版本一:进行资源转移:拷贝与赋值运算符重载意味着资源的转移,在得到资源后,之前的资源所有者断开连接;多个对象存在拷贝转移,无法同时访问资源;具有独占性;
  • 版本二:进行资源管理权转移:加入_owner变量表示资源拥有者,资源拥有者才可以释放,但其他对象都可以使用资源;拷贝与赋值运算符重载意味着资源所有权的转移,有可能造成野指针;资源拥有着释放后,其他对象依然有可能操作,变成野指针操作;
    ps:后来又从版本2还原为版本1,因为野指针伤不起啊o(╥﹏╥)o;
  • 建议使用场景:鉴于上述方法存在明显的缺陷,尽量不使用auto_ptr;

C++11-unique_ptr:单对象独占资源(唯一指针)

  • 为了替换不安全的auto_ptr,从源头上制止了资源转转移后访问失效的问题;还可以自定义删除操作/作为容器元素/管理动态数组;
  • 禁止调用拷贝构造,赋值运算符重载的方式共享资源;禁止方式:私有域声明不定义(防止类外自行定义,防止友元函数)/删除函数
  • 建议使用场景:一份资源被一个对象独占不分享的情况下可以使用;

C++11-shared_ptr:共享智能指针

  • 为了解决前两者资源独占不能共享而产生;

  • 解决浅拷贝问题:使用引用计数的方式来解决浅拷贝问题并实现共享资源
    在内部为每个资源维护一份计数,表示被几个对象共享;
    在引用资源的对象销毁时,说明该对象不再使用资源,资源的引用计数减一;
    如果引用计数是0,说明需要释放该资源了,如果不是,则不释放,防止其他正在使用的对象出现野指针;

  • 解决管理任意类型指针的问题:仿函数的方式定制删除器:
    资源可以malloc,new,文件指针,套接字,这些的释放方式都不同,要让用户根据需要自行选择

  • 解决线程安全问题:
    智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作得是线程安全的
    智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题

  • 解决循环引用的问题:
    当我们直接使用shared_ptr来管理双向链表的资源,就会出现节点的资源泄露的问题:

#include<memory>
#include<iostream>
struct ListNode {
	public:
	ListNode () 
		:_data (0) , _pPre (nullptr) , _pNext (nullptr)
	{ }
	~ListNode () { std::cout << "~ListNode()" << std::endl; };
	int _data;
	std::shared_ptr<ListNode> _pPre;
	std::shared_ptr<ListNode> _pNext;
};
int main () {
	std::shared_ptr<ListNode> node1 (new ListNode);
	std::shared_ptr<ListNode> node2 (new ListNode);
	std::cout << node1.use_count () << std::endl;
	std::cout << node2.use_count () << std::endl;
	node1->_pNext = node2;
	node2->_pPre = node1;//此处成环
	std::cout << node1.use_count () << std::endl;
	std::cout << node2.use_count () << std::endl;
	return 0;
}

我们来用图说明问题发生的过程:
在这里插入图片描述

  1. sp1和sp2两个智能指针分别指向node1,node2,引用计数此时各为一;
  2. node1的next指向node2,node2的pre指向node1,此时各自的引用计数为2;
  3. 当sp2执行析构时,引用计数-1还剩1,所以sp2不释放node2资源直接退出;
  4. 同理当sp1执行析构时,不释放node1直接退出;
  5. 此时sp1,sp2都已经退出,但node1,node2一个都没有释放,资源泄露,这就是循环引用问题
  • 解决方法
    在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了

C++11-weak_ptr:弱指针:专门为了打破循环引,和shared_ptr配合使用

  • 可以从一个shared_ptr或者另一个weak_ptr对象构造而来。
  • weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,因此取名为weak,表明其是功能较弱的智能指针。
  • 协助shared_ptr工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着weak_ptr只对shared_ptr 进行引用,而不改变其引用计数,当被观察的shared_ptr失效后,相应的weak_ptr也相应失效。
    使用了weak_ptr后,上面例子的过程就会变成:
    [外链图片转存失败(img-fRhTMOBI-1564916864450)(en-resource://database/761:1)]
  1. sp1和sp2两个智能指针分别指向node1,node2,因为使用了weak_ptr,use对应shared_ptr的引用个数,weak对应weak_ptr的引用个数,创建时的初始值都为1;
  2. node1的next指向node2,node2的pre指向node1,因为next和pre都是weak_ptr,所以各自的weak引用计数+1,use不变;
  3. 当sp2执行析构时,use-1为0,说明之后将没有shared_ptr类型的对象在用node2了,之中依次执行next和pre这两个weak_ptr的析构,node2的_pNext为空,直接调用其析构,_pPre的计数器(sp1的weak)减1后不为0,_pPre释放但其指向的node1资源不释放,此时node2释放完毕,sp2的weak-1
  4. 同理当sp1执行析构时,use-1=0,之中执行_pNext的析构,其计数器(sp2的weak-1),此时sp2的weak为0,use为0,sp2释放,_pPre为空,直接析构,node1释放完毕,sp1的weak-1,此时sp1weak=0,use=0,sp1释放
  5. sp1,sp2都已经释放,node1,node2也已经释放,循环引用问题被打破

ps:weak_ptr将观测者和所有者区分开来,观测者观测资源不增加shared_ptr的计数,而是增加weak_ptr,使得所有者调用析构时,就一定可以释放资源,最终打破循环引用问题,将双锁问题转化为单锁从而避免类死锁现象;

自行实现

auto_ptr

template<class T>
class AutoPtr {
	public:
	AutoPtr (T* ptr = NULL)
		: _ptr (ptr) {
	}
	~AutoPtr () {
		if ( _ptr )
			delete _ptr;
	}
	// 一旦发生拷贝,就将ap中资源转移到当前对象中,然后另ap与其所管理资源断开联系,
	// 这样就解决了一块空间被多个对象使用而造成程序奔溃问题
	AutoPtr (AutoPtr<T>& ap)
		: _ptr (ap._ptr) {
		ap._ptr = NULL;
	}
	AutoPtr<T>& operator=(AutoPtr<T>& ap) {
		// 检测是否为自己给自己赋值
		if ( this != &ap ) {
			// 释放当前对象中资源
			if ( _ptr )
				delete _ptr;
			// 转移ap中资源到当前对象中
			_ptr = ap._ptr;
			ap._ptr = NULL;
		}
		return *this;
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
	private: T* _ptr;
};
int main () {
	AutoPtr<int> ap (new int);
	// 现在再从实现原理层来分析会发现,这里拷贝后把ap对象的指针赋空了,导致ap对象悬空
	// 通过ap对象访问资源时就会出现问题。
	AutoPtr<int> copy (ap);
	*ap= 10;
	return 0;
}

unique_ptr

#include<fstream>
#include<iostream>

namespace Delector {//删除器
	template<class T>
	struct DFDel {//默认是new用delete删除
		void operator()(T*& ptr) {
			if ( ptr ) {
				delete ptr;
				ptr = nullptr;
			}
		}
	};
	template<class T>
	struct Free {//处理maloc的资源
		void operator()(T*& ptr) {
			if ( ptr ) {
				free (ptr);
				ptr = nullptr;
			}
		}
	};
	struct FClose {
		void operator()(FILE*& pf) {//处理文件流指针和套接字;
			if ( pf ) {
				fclose (pf);
				pf = nullptr;
			}
		}
	};
}

template<class T , class DE = Delector::DFDel<T>>
class Unique_ptr {
	public:
	Unique_ptr (T* p = nullptr)
		:ptr (p) {
	}
	~Unique_ptr{
		DE (ptr);
	}
		T& operator*() {
		return *ptr;
	}
	T* operator->() {
		return ptr;
	}
	private:
	Unique_ptr (const Unique_ptr<T,DE>& s) = delete;
	Unique_ptr& operator=(const Unique_ptr<T,DE>&) = delete;
	private:
	T* ptr;
};

shared_ptr

#include<iostream>
#include<fstream>
#include<mutex>

namespace Delector {//删除器
	template<class T>
	struct DFDel {//默认是new用delete删除
		void operator()(T*& ptr) {//仿函数(重载())实现不同的释放资源的操作
			if ( ptr ) {
				delete ptr;
				ptr = nullptr;
			}
		}
	};
	template<class T>
	struct Free {//处理maloc的资源
		void operator()(T*& ptr) {
			if ( ptr ) {
				free (ptr);
				ptr = nullptr;
			}
		}
	};
	struct FClose {
		void operator()(FILE*& pf) {//处理文件流指针和套接字;
			if ( pf ) {
				fclose (pf);
				pf = nullptr;
			}
		}
	};
}

template<class T , class DF = Delector::DFDel<T>>
class SharedPtr {
public:
	SharedPtr (T* ptr = nullptr)
		: _ptr (ptr)
		, _pCount (nullptr)
		, _pMutex (nullptr) {
		if ( _ptr ) {
			_pMutex = new std::mutex;
			_pCount = new int (1);
		}
	}
	SharedPtr (const SharedPtr<T,DF>& sp)
		: _ptr (sp._ptr)
		, _pCount (sp._pCount)
		, _pMutex (sp._pMutex) {
		AddRef ();
	}
	~SharedPtr () { Release (); }
	SharedPtr<T,DF>& operator=(const SharedPtr<T,DF>& sp) {
		if ( this != &sp ) {
			// 释放管理的旧资源
			Release ();
			// 共享管理新对象的资源,并增加引用计数
			_ptr = sp._ptr;
			_pCount = sp._pCount;
			AddRef ();
		}
		return *this;
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
	int UseCount () { return *_pCount; }
	T* Get () { return _ptr; }
private:
	void AddRef () {//线程安全的++操作
		_pMutex->lock ();
		++*_pCount;
		_pMutex->unlock ();
	}
	int SubRef () {//线程安全的--操作
		_pMutex->lock ();
		--*_pCount;
		_pMutex->unlock ();
		return *_pCount;
	}
	void Release () {//删除函数
		if ( _ptr && 0 == SubRef () ) {
			DF ()(_ptr);
			delete _pCount;
			if ( _pMutex )
				delete _pMutex;
		}
	}
private:
	int* _pCount; // 引用计数
	T* _ptr; // 指向管理资源的指针
	std::mutex * _pMutex;//加锁线程安全
};

int main () {
	int * a = (int *)malloc (100 * sizeof (int));
	SharedPtr<int , Delector::Free<int>> test (a);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值