c++之浅析智能指针

     在c++中,当一个函数发现自己无法处理的错误时就抛出异常,然后对其进行捕捉,此时我们设计的执行流就会发生改变,这样会很容易引发内存泄漏的问题。下面就举一个小例子:

#include<iostream>
using namespace std;
void fun()
{
	int* ptr=new int(1);
	if(1)
	{
		throw 1;
	}
	delete ptr;
}
int main()
{
	try
	{
		fun();
	}
	catch(...)
	{
		cout<<"未知异常"<<endl;
	}
	system("pause");
	return 0;
}

        对于上面的一个小程序,会打乱执行流,ptr就不会释放,引发内存泄漏。此时智能指针就登场了。说起智能指针,很多人会误认为智能指针就是指针,然而它并非指针,是一个模板。由智能指针实例化出来的对象具有和普通指针相似的行为(管理一块空间),所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放,不再通过手动去释放。

       智能指针是通过定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放,这也就是RAII(Resource Acquisition Is Initialization)实现机制。

1.老版的智能指针auto_ptr是通过及时将之前的指针置成空,将这块空间的所有权交给现在的指针(权限转移)的方式来实现的,为防止一块空间释放两次浅拷贝导致的崩溃情况。下面通过简单模拟实现auto_ptr来体会一下它的实现过程。

template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ptr=0):_ptr(ptr)
	{}
	AutoPtr( AutoPtr<T>& ap)        //权限转移
	{
		_ptr=ap._ptr;         
		ap._ptr=NULL;
	}
	AutoPtr<T>& operator=(AutoPtr<T>& ap)
	{
		//std::swap(ap._ptr,_ptr);
		if(_ptr!=ap._ptr)             //权限转移
		{
			delete _ptr;
			_ptr=ap._ptr;
			ap._ptr=NULL;
		}
		return *this;
	}
	~AutoPtr()
	{
		if(_ptr!=NULL)
		{
			delete _ptr;
			_ptr=NULL;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	T* Release()     
	{
		T* tmp=_ptr;
		_ptr=NULL;
		return tmp;
	}
	void Reset(T* p=0)      //释放原来空间,开辟新空间
	{
		delete _ptr;
		_ptr=p;
		p=NULL;
	}
private:
	T* _ptr;
};
下来在测试时,我们讨论以下几个问题:

struct A
{
	int a;
	void Print()
	{
      cout<<"hello world"<<endl;
	}
};
void test()
{
	A* ptr=new A;
	AutoPtr<A> ap1(ptr);
	AutoPtr<A> ap2(ap1);
	ap1->Print();          //不采用->->的形式访问,编译器作了优化
	ap2->Print();
	AutoPtr<A> ap3;
	ap3=ap1;
	ap3->Print();
}


(1) ->操作符,为何在这里不采用ap1->->Print()的调用方式?实际上是编译器做了优化.

       箭头操作符看起来像二元操作符,接受一个对象或一个成员名。实际上,箭头操作符的右操作符并不是一个表达式,而是类成员的标识符,编译器自动将一个标识符传递给函数以获取类成员的工作.ap1是对象,调用operator->,返回T*(A*),是一个指针,接下来再调用A的成员函数Print().

(2)原始auto_ptr可以实现注释行,修改release函数(释放原有空间返回一块新空间)后无法执行。

void test1()
{
	int* ptr=new int(4);
	AutoPtr<int> ap1(ptr);
	AutoPtr<int> ap2(ap1);
	cout<< *ap1<< endl;
	//cout<< *ap2<< endl;     //原始auto_ptr可实现该行,修改release()后无法执行
}
T* Release()     
	{
		T* tmp=_ptr;
		_ptr=NULL;
		return tmp;
	}


(3)auto_ptr无法实现数组的应用,像后来c++11引进的scoped_ptr,shared_ptr都有对应scoped_array,shared_array数组的实现。

(4)早期auto_ptr通过增加(bool owner;)来实现权限转移,但是存在着缺陷.例如:

 AutoPtr<int> ap1;

if(……)

{

     AutoPtr<int> ap2(ap1);

     ……

}

出了作用域后ap2会释放空间还给系统,但ap2仍指向这块空间,会出现野指针。对于这种情况,我们还要在析构函数上做以改进,在owner为false时才释放等等方法,反而麻烦。所以慢慢改进后就不再用这种方式了。

下面把代码给出作为对比了解吧。

#include<iostream>
using namespace std;
 
template<class T>
class AutoPtr
{
public:
    AutoPtr(T* ptr = NULL)
        :_ptr(ptr)
        , _owner(true)
    {}
 
    AutoPtr(const AutoPtr<T>& ap)
        :_ptr(ap._ptr)
    {
        ap._owner = false;
        _owner = true;
    }
 
    AutoPtr<T>& operator=(const AutoPtr<T>& ap)
    {
        if (_ptr!=ap._ptr)
        {
            delete _ptr;
            _ptr = ap._ptr;
            ap._owner = false;
            _owner = true;
        }
        return *this;
    }
 
    ~AutoPtr()
    {
        if (_ptr)
        {        
            delete _ptr;
            _ptr = NULL;
            _owner = false;
        }
    }
 
    T* operator->()
    {
        return _ptr;
    }
 
    T& operator*()
    {
        return *_ptr;
    }
private:
    T* _ptr;
    bool _owner;
};
 
void Test()
{
    AutoPtr<int> ap1(new int(1));
    AutoPtr<int> ap2(ap1);
    AutoPtr<int> ap3 = ap1;
}
 
int main()
{
    Test();
    system("pause");
    return 0;
}



2.新的c++11标准中引入了Boost库中的智能指针,如图:




如果要用Boost库中的智能指针首先我们得把库文件包含进VC中才能用,这里就不再详细赘述。

下来我们先简单实现一下自己的scoped_ptr/shared_ptr

template<typename T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr=0):_ptr(ptr)
	{}
	~ScopedPtr()
	{
		if(_ptr!=NULL)
		{
			delete _ptr;
			_ptr=NULL;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	void Reset(T* sp=0)
	{
		delete _ptr;
		_ptr=sp;
		sp=NULL:
	}
	void Swap(ScopedPtr<T> &sp)
	{
		std::swap(_ptr,sp._ptr);
	}
protected:
	ScopedPtr(const ScopedPtr<T>& sp);        //防拷贝
	ScopedPtr<T>& operator=(const ScopedPtr<T>& sp); //防赋值
private:
	T* _ptr;
};


scoped_ptr/scoped_array最大的特点在于不能赋值,不能拷贝构造.因此我们在实现它的时候必须防赋值,防拷贝构造,在这里采用将赋值操作和拷贝构造函数声明为私有或保护成员来达到目的,也许会有人说我声明为公有成员但是不实现该操作一样可以达到效果啊,但是在类继承后如果实现该操作了呢,那又会引起问题,所以声明为私有或保护才是最安全的。

scoped_array的实现与scoped_ptr的差别在于析构时delete[ ] _ptr(数组形式),没有*和->的重载,而是[ ]的重载。

3.shared_ptr/shared_array:通过引用计数的方式来实现多个指针指向同一块空间的问题。

以下是自己实现的SharedPtr:

template<class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr):_ptr(ptr),_pCount(new int(1))
	{}
	SharedPtr(const SharedPtr<T>& sp)
	{
		_ptr=sp._ptr;
		_pCount=sp._pCount;
		++(*_pCount);
	}
	/*SharedPtr<T>& operator=( SharedPtr<T> sp)
	{
		if(_ptr!=sp._ptr)
		{
			if(--(*_pCount)==0)
			{
				delete _ptr;
				delete _pCount;
				_ptr=NULL;
				_pCount=NULL;
			}
			_ptr=sp._ptr;
			_pCount=sp._pCount;
			++(*_pCount);
		}
		return *this;
	}*/
	//现代写法
	SharedPtr<T>& operator=( SharedPtr<T> sp)
	{
		std::swap(sp._ptr,_ptr);
		std::swap(sp._pCount,_pCount);
		return *this;
	}
	~SharedPtr()
	{
		if(--(*_pCount)==0)
		{
			delete _ptr;
			delete _pCount;
			_ptr=NULL;
			_pCount=NULL;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pCount;
};


虽然shared_ptr已经解决了一些问题,但是任何东西都会存在一定的缺陷。这个智能指针引入了:

(1)循环引用问题;

(2)定制删除器问题;

(3)线程问题.

&下来看一个循环引用的特殊场景:

#include<iostream>
using namespace std;


#include<boost/shared_ptr.hpp>
using namespace boost;

struct Node
{
	~Node()
	{
		cout<<"~Node()"<<endl;
	}
	int _data;
	boost::shared_ptr<Node> _next;
	boost::shared_ptr<Node> _prev;
};
void test()
{
	boost::shared_ptr<Node> sp1(new Node);
	boost::shared_ptr<Node> sp2(new Node);
	cout<<sp1.use_count()<<endl;
	cout<<sp2.use_count()<<endl;
	sp1->_next=sp2;
	sp2->_prev=sp1;
	cout<<sp1.use_count()<<endl;
	cout<<sp2.use_count()<<endl;
}
int main()
{
	test();
	system("pause");
	return 0;
}


居然没有调用析构函数......


        通过上图,可以知道该场景用shared_ptr将会引入循环引用的问题,那么该通过什么方式解决呢?通常我们引入weak_ptr来辅助shared_ptr解决此类问题,注意weak_ptr不能单独使用。weak_ptr是一种不控制所指向对象的生命周期的智能指针,它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到该对象而shared_ptr的引用计数并不发生改变。一旦指向对象的shared_ptr被销毁,对象就被彻底销毁,即使有weak_ptr指向的对象依然会被delete.

下面是基于上面的代码做出的改正,仅仅将_next,_prev改为weak_ptr即可.

struct Node
{
	~Node()
	{
		cout<<"~Node()"<<endl;
	}
	int _data;
	//boost::shared_ptr<Node> _next;
	//boost::shared_ptr<Node> _prev;
	boost::weak_ptr<Node> _next;        
	boost::weak_ptr<Node> _prev;
};


&通常普通指针都是用来维护一块新开辟的空间,当然shared_ptr也是可以的,但是我们不能狭隘的理解为智能指针只能管理动态内存,更准确的来说它是用来管理资源的。比如以下场景:

void fun()
{
	FILE* pf=fopen("test.txt","w");
	if(1)
	{
		throw 1;
	}
	fclose(pf);
}void test()
{
	try
	{
		fun();
	}
	catch(...)
	{
		cout<<"未知异常"<<endl;
	}
}


对于此文件操作,异常的产生,执行流的改变肯定无法关闭文件,内存就有可能泄漏。这种情况普通指针就无法解决了,为了处理这样一个问题首先我们得想如何让程序结束后自动关闭文件呢?好像析构函数可以啊!!

但是析构函数又是类里面的成员函数,这该怎么办呢?在此,得引入仿函数的概念,仿函数又是什么???下面举个例子来说说:

template<typename T>
struct Equal
{
	bool operator()(const T& left,const T& right)
	{
		return left==right;
	}
};
void test()
{
	Equal<int> eq;
	cout<< eq(1,1) <<endl;       //用调用函数的方式
}


仿函数并非函数,就像智能指针并非指针一样,说白了就是重载了()而已,然后进行传参。又因为仿函数的实现在类里,所以析构函数不就也有了么,到这里我们的定制删除器也就登场了,下来就简单实现一下:

struct Default               //仿函数,用来处理指针的释放
{
	void operator()(void* ptr)
	{
		delete ptr;
		ptr=NULL;
	}
};

struct Fclose
{
	void operator()(void* ptr)
	{
		fclose((FILE*)ptr);     //ptr维护文件
	}
};

struct Free
{
	void operator()(void* ptr)
	{
		free(ptr);
		ptr=NULL;
	}
};

template<typename T,typename D = Default>
class SharedPtr
{
public:
	SharedPtr(T* ptr, D del=Default())
		   :_ptr(ptr)
		   ,_pcount(new int(1))
		   ,_del(del)
	{}
	SharedPtr(const SharedPtr<T,D>& sp)
	{
		_ptr=sp._ptr;
		_pcount=sp._pcount;
		++(*_pcount);
	}
	SharedPtr<T,D>& operator=(SharedPtr<T,D> sp)
	{
		std::swap(sp._ptr,_ptr);
		std::swap(sp._pcount,_pcount);
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	~SharedPtr()
	{
		Release();
	}
	int UseCount()
	{
		return *_pcount;
	}
protected:
	void Release()
	{
		if(--(*_pcount)==0)
		{
			delete(_ptr);
			_ptr=NULL;
			delete _pcount;
			_pcount=NULL;
		}
	}
private:
	T* _ptr;
	int* _pcount;
	D _del;
};

void test1()
{
	int* ptr=new int(1);
	SharedPtr<int> sp1(ptr);
    SharedPtr<int> sp2(sp1);
	cout<< *sp1 <<endl;
	cout<< *sp2 <<endl;
}
void test2()
{
	SharedPtr<FILE,Fclose> sp1(fopen("test.txt","w"),Fclose());
}
void test3()
{
	int* ptr=(int*)malloc(sizeof(int));
	SharedPtr<int,Free> sp(ptr,Free());
}








  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值