温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

RAII&智能指针

发布时间:2020-08-08 00:18:47 来源:网络 阅读:1098 作者:nna_0914 栏目:编程语言

RAII:资源分配及初始化。但是这个翻译并没有显示出这个惯用法的真正内涵。RAII的好处在于它提供了一种资源自动管理的方式,当出现异常,回滚等现象时,RAII可以正确的释放资源。

内存泄漏会导致:

            1.内存耗尽 2.其他程序可能用不了了 3.程序崩溃

在资源的获取和释放之间,我们通常会使用资源,但常常一些不可预计的异常会在资源使用过程中产生,这就使资源没有得到正确的释放。但是我们其实是可以捕捉异常的,但是捕捉异常会使得代码冗余杂乱,量大而且可读性比较低。

比如:

void DoSomething()
{
    int *p=new int(1);
    cout<<"DoSomething()"<<endl;
}

void Test()
{
   try
   {
      DoSomething();
   }
   catch(...)
   {
      delete p;
      throw;
   }
}

这样短短一个代码看起来却很杂乱,而且也不好控制。

所以我们引出了智能指针。智能指针的实现原理就是RAII。

智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

下面介绍几个Boost库中的智能指针。

  1. AutoPtr

AutoPtr的实现主要是管理权转移。它没有考虑引用计数,当用一个对象构造另一个对象时,会转移这种拥有关系。

template<class T>
class AutoPtr
{
	//friend ostream& operator<< <T>(ostream& os, const AutoPtr<T>& ap);
public:
	AutoPtr(T* ptr)
		:_ptr(ptr)
	{}
	~AutoPtr()
	{
		if (_ptr!=NULL)
		{
			delete _ptr;
		}
	}
	AutoPtr(AutoPtr<T>& ap)
		:_ptr(ap._ptr)
	{
		ap._ptr = NULL;
	}
public:
	AutoPtr<T>& operator=(const AutoPtr<T>& ap)
	{
		if (this != ap)
		{
			delete _ptr;
			_ptr = ap._ptr;
			ap._ptr = NULL;
			return _ptr;
		}
	}

	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
public:
	T* GetPtr()
	{
		return _ptr;
	}

private:
	T* _ptr;
};

旧版的AutoPtr在它的成员函数中有一个变量owner,在构架对象的时候owner为true。当用它去构建新的对象时,他自己的owner变为false,新对象的owner为true。赋值重载的时候也是,新对象的owner是true。这样在析构的时候只要判断owner的状态是否为true,当为true时,将这块空间delete即可。

template<class T>
class AutoPtr
{
public:
	AutoPtr(T* ptr)
		:_ptr(ptr)
		, owner(true)
	{}
	~AutoPtr()
	{
		if (owner)
		{
			delete _ptr;
		}
	}
	AutoPtr(AutoPtr<T>& ap)
		:_ptr(ap._ptr)
		, owner(true)
	{
		ap._ptr = NULL;
		ap.owner = false;
	}
public:
	AutoPtr<T>& operator=(const AutoPtr<T>& ap)
	{
		if (this != ap)
		{
			delete _ptr;
			_ptr = ap._ptr;
			ap.owner = false;
			owner = true;
			return _ptr;
		}
	}
}

看起来旧的比新的好。AutoPtr最大的缺点就是只能有一个指针管理一块空间。那么为什么新的还取代了旧的呢?看下面

AutoPtr<int> ap1(new int(1));
 
 if (1)
 
 {
 
  AutoPtr<int> ap2(ap1);
 
 }
 
 *ap1 = 3;

这段代码是用旧版本实现的智能指针(ap1)指向一个动态开辟的内存,然后在if条件语句中又有一个ap2指向这块内存,我们会知道,根据旧版的智能指针的实现原理,ap1的_owner为false,ap2的_owner为true。那么除了if条件语句的局部作用域,ap2就自动调用析构函数释放内存,那么当我们在外面*ap1=3时,访问到的是一块已经被释放了的内存,那么程序这时就会出现问题。

如果是新版的auto_ptr,它提供了一个公有成员函数GetPtr(),可以获取指针_ptr,当发生这种情况时,它可以先判断_ptr是否为空,然后才去访问内存。旧版本这样做是无用的,因为ap1的_ptr并不为空。


2.ScopePtr//守卫指针

这个类型的指针简单来说就是简单粗暴的防拷贝。

template<class T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr)
		:_ptr(ptr)
	{}
	~ScopedPtr()
	{
		if (_ptr != NULL)
		{
			delete _ptr;
		}
	}
protected:
	ScopedPtr(const ScopedPtr<T>& sp);
	ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
public:
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}

private:
	T* _ptr;
};

将拷贝构造函数和赋值重载函数只声明不实现,而且是Protected.就是为了防止在类外实现的情况发生,简单粗暴哈哈。RAII&智能指针


3.SharedPtr

采用引用计数,构造一个对象时计数器为1,用这个对象去拷贝构造另一个新的对象时,计数器增加1.去赋值给另一个对象时,计数器同样加1.析构时计数器减1.当计数器值为1时,便可delete这块空间。

template<class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}
	~SharedPtr()
	{
		_Release();
	}
	SharedPtr(SharedPtr<T>& sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		(*_pcount)++;
	}
public:
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			_Release();
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			(*_pcount)++;
		}
		return *this;
	}

	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	long UseCount()
	{

		return *(_pcount);
	}
	T* GetPtr()
	{
		return _ptr;
	}

private:
	T* _ptr;
	int* _pcount;
	Del _del;

	void _Release()
	{
		if (--(*_pcount) == 0)
		{
			_del(_ptr);
			delete(_pcount);
		}
	}
};

在这些指针中最常用到的就是SharedPtr了。但是SharedPtr也存在问题。

  1. 线程安全(这个问题我这个级别还不能很好地解决哈哈。等着我变成大神,就办了它。。。)

  2. 循环引用

    什么是循环引用呢??给小伙伴举个例子来说明一下。

void test2()
{
	SharedPtr<Node> cur(new Node(1));
	SharedPtr<Node> next(new Node(2));
	cur->_next = next;
	next->_prev = cur;
}

struct Node
{
	Node(int d)
	:_data(d)
	, _next(NULL)
	, _prev(NULL)
	{}

	int _data;
	SharedPtr<Node> _next;
	SharedPtr<Node> _prev;
};

int main()
{
	test2();
	getchar();
	return 0;
}

运行这段代码回发现并没有输出。没有输出的原因是  cur,next的引用计数都是2,当出了test2的作用域时,分别调用析构函数,二者的引用计数都减为1.但是cur靠next->_prev指着,next靠cur->_next指着,两个都在等对方先delete自己的引用计数才能减到0,自己才能delete,这就导致二者都不会delete了。要解决这个问题呢,我们就要引用第四种只能指针了。。那就是WeakPtr了。WeakPtr可以说就是为了SharedPtr准备的。因为WeakPtr的构造函数只接受SharedPtr类型的对象。

struct Node
{
	Node(int d)
	:_data(d)
	, _next(NULL)
	, _prev(NULL)
	{}

	int _data;
	WeakPtr<Node> _next;
	WeakPtr<Node> _prev;
};

WeakPtr不增加引用计数。这样next->_prev和cur->_next两个指针就不会增加引用计数,也就不会出现循环引用的问题了。

3.定制删除器

当SharedPtr类型的指针遇到其他有些类型的指针时,它就不行了。。。。哦哦。。为什么说他不行了呢,因为SharedPtr只能解决new 出来的东西。那么对于文件类型的指针呢,malloc出来的东西呢。这就需要我们为他们量身定制解决方法啦。

在这里,又要讲到另外一个知识点了。那就是仿函数。它就是能把对象当做函数调用一样的使用。

template<class T>
struct Less
{
   bool operator()(const T& l,const T& r)
   {
      retrun l>r;
   }
}

int main()
{
   Less less;
   cout<<less(1.2)<<endl;//使用对象去调用类里面的函数
}

接下来就是定制删除器

template<class T ,class Del = Delete<T>>
class SharedPtr
{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}
	SharedPtr(T* ptr,Del del)
		:_ptr(ptr)
		, _pcount(new int(1))
		, _del(del)
	{}
	~SharedPtr()
	{
		_Release();
	}
	SharedPtr(SharedPtr<T,Del>& sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		(*_pcount)++;
	}
public:
	SharedPtr<T, Del>& operator=(const SharedPtr<T, Del>& sp)
	{
		if (_ptr != sp._ptr)
		{
			_Release();
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			(*_pcount)++;
		}
		return *this;
	}

	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	long UseCount()
	{

		return *(_pcount);
	}
	T* GetPtr()
	{
		return _ptr;
	}

private:
	T* _ptr;
	int* _pcount;
	Del _del;

	void _Release()
	{
		if (--(*_pcount) == 0)
		{
			_del(_ptr);
			delete(_pcount);
		}
	}
};

template<class T>
struct Free                          //free
{
	void operator()(void* ptr)
	{
		cout << "free" << endl;
		free(ptr);
		ptr = NULL;
	}
};

template<class T>
struct Delete                         //delete
{
	void operator()(T* ptr)
	{
		cout << "Del" << endl;
		delete(ptr);
		ptr = NULL;
	}
};

template<class T>
struct Fclose                            //fclose
{
	void operator()(void* ptr)
	{
		cout << "Fclose" << endl;
		fclose((FILE*)ptr);
	}
};

最后我们来总结一下:

  1. AutoPtr   管理权转移-》不要使用

  2. ScopePtr  防拷贝-》简单粗暴

  3. SharedPtr 引用计数-》增减引用计数,最有一个对象释放

  4. xxxArray  管理对象数组-》operator[]

  5. WearPtr   弱指针,辅助SharedPtr.解决循环引用


说了这么多,比较啰嗦哈RAII&智能指针

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI