学习->C++篇十九:四种智能指针及其实现

目录

为什么需要智能指针?

什么是内存泄露?

如何避免内存泄露?

什么是RAII?

RAII有什么用?

智能指针的原理是什么?

C++的智能指针有哪些?

auto_ptr

unique_ptr

shared_ptr

weak_ptr


为什么需要智能指针?

先看下面的代码:

        如果fun()函数抛出了异常,根据上篇异常的知识,当前代码块没有捕获异常,那么异常就会到上一个函数调用栈,也就是调用test函数的代码块匹配异常,那么m1和m2指向的空间就没有人释放,于是会造成内存泄露。

什么是内存泄露?

        因为疏忽或者错误,进程不能释放已经不再使用的内存的情况。

显然内存泄漏是不能容忍的,因为长期运行的进程,如果出现内存泄露,会影响很大,导致系统响应越来越慢,最终卡死,因为内存空间不断的减少。比如操作系统,后台服务。

内存泄露的种类:

    堆内存的泄漏

    系统资源的泄露,比如套接字,文件描述符,管道

如何避免内存泄露?

    - 编码规范,简单而言就是申请的内存务必记得释放,但从上面的代码可看出,如果遇到了异常也不能解决异常导致的问题,于是需要下面的方法。

    - 采用RAII思想或者智能指针来管理资源

    - 使用内存管理库

    - 使用内存泄露检测工具

什么是RAII?

RAII:即Resource Acquisition Is Initialization资源获取即初始化,一种利用对象的生命周期来控制资源的技术。注意,这个思想是后面实现智能指针的原理。

RAII有什么用?

- 不需要显式释放资源

- 对象所需要的资源在他的生命周期内中保持有效

智能指针的原理是什么?

1.利用RAII特性:让对象的生命周期去控制我们资源的生命周期

2.面向对象思想,智能指针用类实现,需要重载运算符让智能指针有像指针一样的行为

C++的智能指针有哪些?

基于不同的场景,智能指针的实现和使用不同将智能指针进行分类。

(注意:实现不同的本质是管理指针的方法不同,这里主要体现在如何拷贝智能指针对象上)

auto_ptr

C++98版本提供了auto_ptr智能指针

- 它的拷贝构造函数和赋值重载函数是基于管理权转移的思想实现的

实现如下:

template <class T>//智能指针要管理不同类型的指针,实现为类模板
class auto_ptr//基于管理权转移的思想
{
	typedef auto_ptr<T> self;
public:
	auto_ptr(T* ptr)
		:_ptr(ptr)
	{}

	auto_ptr(self& s)
		:_ptr(s._ptr)//转移指针
	{
		s._ptr = nullptr;
	}

	self& operator=(self& s)
	{
		if (_ptr)delete _ptr;//直接释放原指针
		_ptr = s._ptr;//转移指针
		s._ptr = nullptr;
        return *this;
	}
	
	~auto_ptr()
	{
		if (_ptr)
			delete _ptr;
	}

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

C++11参考了boost库中智能指针的实现,实现了三种智能指针:

unique_ptr

unique_ptr基于防拷贝的思路,直接使用关键字delete删除拷贝构造函数和赋值重载函数

实现如下:

template <class T>//智能指针要管理不同类型的指针,实现为类模板
class unique_ptr//基于禁止拷贝的思想
{
	typedef unique_ptr<T> self;
public:
	unique_ptr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

	unique_ptr(const self& s) = delete;
	self& operator=(const self& s) = delete;

	~unique_ptr()
	{
		if (_ptr)
			delete _ptr;
	}

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

shared_ptr

可以拷贝的智能指针,shared_ptr利用引用计数,记录指向资源的指针数目,对象析构的时候计数减一,当引用计数为0的时候才释放资源,于是可以实现如下:

template <class T>//智能指针要管理不同类型的指针,实现为类模板
class shared_ptr//利用引用计数实现允许拷贝的智能指针
{
	typedef shared_ptr<T> self;
public:
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		,_pcount(new int(1))
	{}

	shared_ptr(self& s)
		:_ptr(s._ptr)
		,_pcount(s._pcount)
	{
		++(*_pcount);
	}

	self& operator=(const self& s)
	{
		if (_ptr != s._ptr)
		{
			if (--(*_pcount) == 0 && _ptr && _pcount)//当前指针计数--,s的指针计数++
            {
                delete _pcount;
				delete _ptr;
            }
			_ptr = s._ptr;
			_pcount = s._pcount;
			++(*_pcount);
		}
        return *this;
	}

	~shared_ptr()
	{
		if (--(*_pcount) == 0 && _ptr && _pcount)
		{
			delete _ptr;
			delete _pcount;
		}
	}

	T* operator->() { return _ptr; }
	T operator*() { return *_ptr; }
	int use_count() { return *_pcount; }//返回引用计数
	T* get() const { return _ptr; }//返回指针
private:
	T* _ptr;
	int* _pcount;//引用计数,当不同对象指针相同时,
	//其计数的地址需要一样,计数大小才能同步,故存储在堆空间
};

因为引用计数存储在堆空间,改变引用计数的函数,有线程安全问题,为了保持线程安全,底层要有互斥锁。

于是可以实现线程安全的版本如下:

#include <mutex>
using std::mutex;
template <class T>//智能指针要管理不同类型的指针,实现为类模板
class shared_ptr//利用引用计数实现允许拷贝的智能指针
{
	typedef shared_ptr<T> self;
public:
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		,_pcount(new int(1))
		,_pmutex(new mutex)
	{}

	shared_ptr(const self& s)
		:_ptr(s._ptr)
		,_pcount(s._pcount)
		,_pmutex(s._pmutex)
	{
		(*_pmutex).lock();
		++(*_pcount);
		(*_pmutex).unlock();
	}

	void Release()
	{
		(*_pmutex).lock();
		bool flag = false;//判断是否需要释放锁,因为这里不能在临界区释放锁
		if (--(*_pcount) == 0 && _ptr && _pcount)//当前指针计数--,s的指针计数++
		{
			delete _ptr;
			delete _pcount;
			flag = true;
		}
		(*_pmutex).unlock();

		if (flag)
			delete _pmutex;
	}

	self& operator=(const self& s)
	{
		if (_ptr != s._ptr)
		{
			Release();
			_ptr = s._ptr;
			_pcount = s._pcount;
			_pmutex = s._pmutex;
			(*_pmutex).lock();
			++(*_pcount);
			(*_pmutex).unlock();
		}
		return *this;
	}

	~shared_ptr()
	{
		Release();
	}

	T* operator->() { return _ptr; }
	T operator*() { return *_ptr; }
	int use_count() { return *_pcount; }//返回引用计数
	T* get() const { return _ptr; }//返回指针
private:
	T* _ptr;
	int* _pcount;//引用计数,当不同对象指针相同时,
	//其计数的地址需要一样,计数大小才能同步,故存储在堆空间
	mutex* _pmutex;//保证线程安全
};

注意:智能指针内部实现了加锁,故是线程安全的,但是指针指向的资源不是线程安全的,需要访问的人处理。

weak_ptr

什么是循环引用问题?

比如如下代码:

struct listnode
{
	shared_ptr<listnode> _prev=shared_ptr<listnode>(nullptr);
	shared_ptr<listnode> _next= shared_ptr<listnode>(nullptr);
	int _data = 0;
	~listnode()
	{
		std::cout << "~listnode" << std::endl;
	}
};

int main() 
{
	shared_ptr<listnode>n1 = new listnode;
	shared_ptr<listnode>n2 = new listnode;
	n2->_prev = n1;
	n1->_next = n2;
	return 0;
}

节点中两个指针用智能指针来实现,当节点相内部指针互指向的时候,节点的引用计数都增加到2,析构时智能指针释放,节点引用计数都减为1,所以两个节点的资源都不会被释放,会造成内存泄露,如图:

 

        为了解决shared ptr指针循环引用的问题,引入weak_ptr,其拷贝构造函数和赋值重载函数参数是shared_ptr类型的对象,拷贝时不会增加其底层的引用计数,即不参与管理我们的资源。上述循环引用问题,只需要将节点内成员shared_ptr 改成 weak_ptr 即可解决。

实现如下:

template <class T>//智能指针要管理不同类型的指针,实现为类模板
class weak_ptr//直接拷贝shared_ptr 的指针,不增加引用计数->不参与资源的管理
{
public:
	weak_ptr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

	weak_ptr(const shared_ptr<T>& s)
		:_ptr(s.get())
	{}

	weak_ptr<T>& operator=(const shared_ptr<T>& s)
	{
		_ptr = s.get();
		return *this;
	}

	~weak_ptr()
	{}

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值