浅谈智能指针

在说智能指针之前,我们先来看以下代码:

//文件——Smart_ptr.h
#include <iostream>
#include <vld.h>
using namespace std;

class Test
{
public:
	Test(int a = 10,int* p = NULL):ma(a),mp(p) {cout<<"Test()"<<endl;}
	~Test() {cout<<"~Test()"<<endl;}
private:
	int ma;
	int* mp;
};

//文件——Smart_ptr.cpp
#include "SmartPtr.h"

int main()
{
	int *p = new int();
	Test A(20,p);

	return 0;
}

我们使用vld工具去查看程序运行过程中是否出现内存泄露,结果如下:


    在上图中可以看到,确实发生了内存泄露。因为我们传入的指针指向的是堆上的内存块,如果在对象析构时不手动释放(delete)的话,就会发生内存泄露。那你或许会说——实现自己的析构函数、进行手动释放不就行了,答案是不行,因为当你传入的p指向的是栈上内存时,就不能使用delete去手动释放,栈上内存是系统来进行管理的。对于指针p传入的不确定性,我们就没办法解决了吗?

    答案就是——智能指针。我们都知道,栈上空间是系统分配、回收的,堆上空间是用户自己分配、回收的,那么如果能对堆上空间实现用户分配、系统自行回收的机制,就不用担心内存泄露的问题了。

    智能指针的思想:定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数中完成资源的清理和释放,可以保证资源的正确使用和释放。(资源指的是堆上资源)

    我们将讲述四个智能指针,其中scoped_ptr、shared_ptr和 weak_ptr是C++11标准从boost库引入的智能指针。

一、auto_ptr

    auto_ptr已经被摒弃了,只是为了向前兼容而被保存了下来,建议代码中不要使用该智能指针。

1.实现一
#include <iostream>
#include <vld.h>
using namespace std;

template <typename T>
class Myauto_ptr
{
public:
	Myauto_ptr(T* p):_mptr(p){}//构造函数
	Myauto_ptr(const Myauto_ptr<T>& p)//拷贝构造函数
	{
		_mptr = p._mptr;
		p._mptr = NULL;
	}
	Myauto_ptr<T>& operator=(const Myauto_ptr<T>& p)//复制运算符的可重载函数
	{
		if(this != &p)
		{
			_mptr = p._mptr;
			p._mptr = NULL;
		}
		return *this;
	}
	T& operator*()//*运算符的可重载函数
	{
		return *_mptr;
	}
	T* operator->()//->运算符的可重载函数
	{
		return _mptr;
	}
	~Myauto_ptr()
	{
		delete _mptr;
		_mptr = NULL;
	}
private:
	T* _mptr;
};

class Test
{
public:
	void Show()
	{
		cout<<"Test::show()"<<endl;
	}
};

Myauto_ptr<int> fun()
{
	Myauto_ptr<int> tmp(new int(80));
	return tmp;
}

int main()
{
	Myauto_ptr<int> pa(new int);
	*pa = 20;
	cout<<*pa<<endl;

	Myauto_ptr<int> pb(pa);
	*pb = 50;
	cout<<*pb<<endl;

	Myauto_ptr<int> pc(fun());//自定义类型的临时对象是 变量
	cout<<*pc<<endl;

	Myauto_ptr<Test> pd(new Test());
	pd->Show();

	return 0;
}
2.实现一的问题

    auto_ptr的原则是“管理权唯一,释放权唯一”。

问题一:拷贝构造函数
    A指针指向了addr内存,B指针通过 拷贝构造的方法 将A指针对addr的管理权和释放权一并拿来,而A指针这时再想去操作addr内存时,却只能崩溃(它的_mptr已经变为NULL),这样非常不合理,A是去构造B的,自己却失去了所有东西......

 

问题二:赋值运算符的可重载函数

    A指针和B指针 都初始化指向addr内存,当用B指针去给A指针赋值时,按照步骤先将A指向的内存释放掉,A再重新指向B指向的内存,最后将B的指向置为NULL,但是A和B管理的是同一片内存,释放掉之后,B指针就是一个野指针,A得到之后去访问 或者 析构时程序就会崩溃。


3.实现二

    原则是“管理权不唯一,释放权唯一”,即可以多个auto_ptr管理同一片内存,但是只能有一个指针释放该片内存。怎么确定是哪个指针能释放呢?在成员变量中加入一个标志变量flag,flag为true则有释放权,flag为false则没有释放权。

管理权转移原则:

① 旧智能指针有释放权,新智能指针有,并且旧的置为没有

② 旧智能指针没有释放权,新智能指针也没有

#include <iostream>
using namespace std;

template <typename T>
class Myauto_ptr2
{
public:
	Myauto_ptr2(T* p)
	{
		_mptr = p;
		_flag = true;
		if(!_mptr)//_mptr==NULL
		{ 
			_flag = false;
		}
	}
	
	Myauto_ptr2(const Myauto_ptr2<T>& p)
	{
		_mptr = p._mptr;
		if(p._flag == true)
		{
			_flag = true;
			p.realease();
		}
		else
		{
			_flag = false;
		}
	}
	Myauto_ptr2<T>& operator=(const Myauto_ptr2<T>& p)
	{
		if(this != &p)
		{
			this->~Myauto_ptr2();//释放旧资源!!
			_mptr = p._mptr;
			if(p._flag == true)
			{
				_flag = true;//释放权转移
				p.realease();
			}
			else
			{
				_flag = false;
			}
		}
		return *this;
	}
	T& operator*()
	{
		return *_mptr;
	}
	T* operator->()
	{
		return _mptr;
	}
	~Myauto_ptr2()
	{
		if(_flag == true)
		{
			delete _mptr;
			_mptr = NULL;
			_flag = false;
		}
	} 
private:
	void realease()const 
	{
		((Myauto_ptr2<T>*)this)->_flag = false;
	}
	T* _mptr;
	bool _flag;//mutable bool _flag;  mutable 关键字保证了_flag就算是const对象中的成员,也可以被修改
};
4.实现二的问题

    根据管理权原则一,若旧智能指针有释放权,则新智能指针将释放权拿走,旧释放权false。那么如果这个新智能指针只是一个临时量,随着生存周期的到来也随之消失,那块被管理的内存岂不是莫名其妙就被释放了?

    问题一:智能指针作为函数的形参——传递实参的过程是初始化过程,可能发生释放权的转移,从而造成智能指针的提前释放
void test(Myauto_ptr2<int> rhs)
{
	cout<<*rhs<<endl;
}

int main()
{
	Myauto_ptr2<int> p(new int(10));
	Myauto_ptr2<int> q(new int(20));

	*p = 11;
	*q = 22;

	p = q;

	Myauto_ptr2<int> w = p;

	test(w);//在传参的过程中,w的释放权发生转移

	cout<<"w"<<*w<<endl;//打印一个随机值

	return 0;
}

二、scoped_ptr

    在auto_ptr中,因为设计的问题,导致在使用拷贝构造函数和 赋值运算符的可重载函数时,发生了访问 空指针、野指针的情况。为了规避这些情况,scoped_str在设计的时候就直接屏蔽了 拷贝构造函数 和 赋值运算符的可重载函数。scoped_ptr的原则——管理权唯一,释放权唯一。

1. 实现
#include <iostream>
using namespace std;

template <typename T>
class Myscoped_str
{
public:
	Myscoped_str(T* p):_mptr(p){}
	T& operator*()
	{
		return *_mptr;
	}
	T* operator->()
	{
		return _mptr;
	}
	~Myscoped_str()
	{
		delete _mptr;
		_mptr = NULL;
	}
private:
	Myscoped_str(const Myscoped_str<T>& rhs);
	Myscoped_str<T>& operator=(const Myscoped_str<T>& rhs);
	T* _mptr;
};
2. 问题

    scoped_ptr的原则也是“管理权唯一,释放权唯一”,因为屏蔽了 拷贝构造函数 和 赋值运算符的可重载函数,所以不可能通过这两种方式去 使得两个及以上的指针指向同一片内存,但是仍然避免不了人为的操作,即初始化两个scope_ptr指向同一片内存当其中一个指针释放了该内存,另一个再想去修改内存的内容的话程序就会崩溃。

三、shared_ptr

    shared_ptr是基于“引用计数”实现的,多个shared_ptr可指向同一片内存,并且它们维护着共享的引用计数器——记录着指向同一片内存的shared_ptr对象的个数。当最后一个指向某一片内存的shared_ptr对象也销毁之后,会自动销毁掉该片内存。shared_ptr很好地解决了 多个智能指针指向一块内存块时, 析构时程序崩溃 的情况。shared_ptr的原则是——管理权不唯一,释放权不唯一。

1. 引用计数器
    我们只需要一个 引用计数器对象,这个对象中就存储了所有关于内存引用的信息,对于引用计数器的类设计就采用 单例模式

class Refmanage
{
public:
	static Refmanage* GetSingle()
		//向外提供的 得到唯一引用计数器对象 的方法
	{
		return &_rm;
	}
public:
	void addref(void* ptr)
	{
		//判空
		if(ptr != NULL)
		{
			int index = Find(ptr);//在引用计数器中找一下改地址是否已有
			if(index < 0)//没找到
			{
				Node tmp(ptr,1);//新创结点
				_arr[_length++] = tmp;//插入引用计数器
			}
			else//找到该地址
			{
				_arr[index]._count++;
			}
			
		}
	}
	int getref(void* ptr)
	{
		//首先一定记得判空
		if(ptr == NULL)
		{
			return 0;
		}
		int index = Find(ptr);
		if(index < 0 )
		{
			return -1;
		}
		return _arr[index]._count;
	}
	void delref(void* ptr)//减少计数
	{
		if(ptr != NULL)
		{
			int index = Find(ptr);
			if(index < 0)
			{
				throw exception("addr is not exist");
			}
			else
			{
				if(_arr[index]._count != 0)
				{
					_arr[index]._count--;
				}
			}
		}
	}
private:
	Refmanage():_length(0)
	{}
	Refmanage(const Refmanage& rhs);
private:
	int Find(void* ptr)
	{
		for(int i=0; i<_length; i++)
		{
			if(_arr[i]._addr == ptr)
			{
				return i;//返回 addr的位置
			}
		}
		return -1;
	}
	class Node
	{
	public:
		Node(void* ptr=NULL, int num = 0):_addr(ptr),_count(num){}
		~Node(){memset(this,0,sizeof(Node));}
	public:
		void* _addr;//引用的内存地址
		int _count; //该内存的引用数
	};
	Node _arr[10];
	int _length;// 引用计数器的有效长度
	static Refmanage _rm; //唯一的引用计数器对象  
};
2. shared_ptr 实现
Refmanage Refmanage::_rm; //前置声明,否则 static Refmanage* rm 编译器不认识

template <typename T>
class Myshared_ptr
{
public:
	Myshared_ptr(T* ptr=NULL):_mptr(ptr)
	{
		AddRef();
	}
	Myshared_ptr(const Myshared_ptr<T>& rhs):_mptr(ptr)
	{
		AddRef();
	}
	Myshared_ptr& operator=(const Myshared_ptr<T>& rhs)
	{
		if(this != &rhs)
		{
			DelRef();
			this->~Myshared_ptr();
			_mptr = rhs._mptr;
			rhs.AddRef();
		}
		return *this;
	}
	~Myshared_ptr()
	{
		DelRef();
		if(GetRef() == 0)
		{
			delete _mptr;
		}
		_mptr = NULL;
	}
	T& operator*()
	{
		return *_mptr;
	}
	T* operator->()
	{
		return _mptr;
	}
private:
	void AddRef()
	{
		rm->addref(_mptr);
	}
	void DelRef()
	{
		rm->delref(_mptr);
	}
	int GetRef()
	{
		return rm->getref(_mptr);
	}
	T* _mptr;
	static Refmanage* rm; //指针
};

//静态成员变量类外初始化
template <typename T>
Refmanage* Myshared_ptr<T>::rm = Refmanage::GetSingle();
3. 问题
    虽然shared_ptr很好地解决了 “多个智能指针指向同一块内存时,析构时出错”的问题,可是同时也带来了新的问题。shared_ptr之间不能相互引用,否则管理的堆上内存块无法释放。
class B;
class A
{
public:
	A()
	{
		cout<<"A::A()"<<endl;
	}
	~A()
	{
		cout<<"A::~A()"<<endl;
	}
	Myshared_ptr<B> pb; //成员对象
};

class B
{
public:
	B()
	{
		cout<<"B::B()"<<endl;
	}
	~B()
	{
		cout<<"B::~B()"<<endl;
	}
	Myshared_ptr<A> pa; //成员对象
};

int main()
{
	Myshared_ptr<A> a(new A());
	Myshared_ptr<B> b(new B());

	a->pb = b;
	b->pa = a;

	return 0;
}
shared_ptr的相互引用造成了 内存泄露:

图解:


四、weak_ptr

    weak_ptr的出现就是为了 解决shared_ptr中“不能相互引用”的问题,因此weak_ptr需要和shared_ptr配合使用。weak_ptr是弱引用,而shared_ptr是一个强引用。
    强引用:只要有一个强引用指向该对象时,该对象就不会被释放。

    弱引用:弱引用只是当对象存在时的一个引用,对象不存在时弱引用能够检测出来,从而避免非法访问。弱引用并不像强引用那样可以进行内存管理(对引用计数没有影响),它的特点就是可以检测到 对象已经不存在。

1. 实现
class Refmanage
{
public:
	static Refmanage* GetSingle()
		//向外提供的 得到唯一引用计数器对象 的方法
	{
		return &_rm;
	}
public:
	void addref(void* ptr)
	{
		//判空
		if(ptr != NULL)
		{
			int index = Find(ptr);//在引用计数器中找一下改地址是否已有
			if(index < 0)//没找到
			{
				Node tmp(ptr,1);//新创结点
				_arr[_length++] = tmp;//插入引用计数器
			}
			else//找到该地址
			{
				_arr[index]._count++;
			}
			
		}
	}
	int getref(void* ptr)
	{
		//首先一定记得判空
		if(ptr == NULL)
		{
			return 0;
		}
		int index = Find(ptr);
		if(index < 0 )
		{
			return -1;
		}
		return _arr[index]._count;
	}
	void delref(void* ptr)//减少计数
	{
		if(ptr != NULL)
		{
			int index = Find(ptr);
			if(index < 0)
			{
				throw exception("addr is not exist");
			}
			else
			{
				if(_arr[index]._count != 0)
				{
					_arr[index]._count--;
				}
			}
		}
	}

private:
	Refmanage():_length(0)
	{}
	Refmanage(const Refmanage& rhs);
private:
	int Find(void* ptr)
	{
		for(int i=0; i<_length; i++)
		{
			if(_arr[i]._addr == ptr)
			{
				return i;//返回 addr的位置
			}
		}
		return -1;
	}
	class Node
	{
	public:
		Node(void* ptr=NULL, int num = 0):_addr(ptr),_count(num){}
		~Node(){memset(this,0,sizeof(Node));}
	public:
		void* _addr;//引用的内存地址
		int _count; //该内存的引用数
	};
	Node _arr[10];
	int _length;// 引用计数器的有效长度
	static Refmanage _rm; //唯一的引用计数器对象  
};
Refmanage Refmanage::_rm;

template <typename T>
class Myshared_ptr
{
public:
	Myshared_ptr(T* ptr=NULL):_mptr(ptr)
	{
		AddRef();
	}
	Myshared_ptr(const Myshared_ptr<T>& rhs):_mptr(ptr)
	{
		AddRef();
	}
	Myshared_ptr& operator=(const Myshared_ptr<T>& rhs)
	{
		if(this != &rhs)
		{
			DelRef();
			this->~Myshared_ptr();
			_mptr = rhs._mptr;
			AddRef();
		}
		return *this;
	}
	~Myshared_ptr()
	{
		DelRef();
		if(GetRef() == 0)
		{
			delete _mptr;
		}
		_mptr = NULL;
	}
	T& operator*()
	{
		return *_mptr;
	}
	T* operator->()
	{
		return _mptr;
	}
	T* Getptr()const
	{
		return _mptr;
	}
private:
	void AddRef()
	{
		rm->addref(_mptr);
	}
	void DelRef()
	{
		rm->delref(_mptr);
	}
	int GetRef()
	{
		return rm->getref(_mptr);
	}
	T* _mptr;
	static Refmanage* rm;
};
//静态成员变量类外初始化
template <typename T>
Refmanage* Myshared_ptr<T>::rm = Refmanage::GetSingle();

template<typename T>
class Myweak_ptr
{
public:
	Myweak_ptr(T* ptr = NULL) :
		_mptr(ptr)
	{}
	
	Myweak_ptr(const Myweak_ptr<T>& rhs)
		:_mptr(rhs._mptr)
	{}
	Myweak_ptr<T>& operator=(const Myweak_ptr<T>& rhs)
	{
		if (this != &rhs)
		{
			_mptr = rhs._mptr;
		}
		return *this;
	}
	Myweak_ptr<T>& operator=(const Myshared_ptr<T>& rhs)
	{
		_mptr = rhs.Getptr();
		return *this;
	}
	~Myweak_ptr()
	{}
	T* operator->()
	{
		return _mptr;
	}
	T& operator*()
	{
		return *_mptr;
	}
private:
	T* _mptr;
};

class B;
class A
{
public:
	A()
	{
		cout<<"A::A()"<<endl;
	}
	~A()
	{
		cout<<"A::~A()"<<endl;
	}
	Myweak_ptr<B> pb;
};

class B
{
public:
	B()
	{
		cout<<"B::B()"<<endl;
	}
	~B()
	{
		cout<<"B::~B()"<<endl;
	}
	Myweak_ptr<A> pa;
};

int main()
{
	Myshared_ptr<A> a(new A());
	Myshared_ptr<B> b(new B());

	a->pb = b;
	b->pa = a;

	return 0;
}

上面代码的执行结果显示  没有内存泄露出现。

2. 问题
    虽然 weak_ptr解决了强引用shared_ptr带来的 “循环引用-->内存泄露”的问题,但是使用weak_ptr的前提是 清楚代码中会出现shared_ptr相互引用的情况,但是并不总是容易看出,而且运行期的互相引用也不总是能够察觉。

    

    
    


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值