RAII和模拟实现智能指针

  • 什么叫RAII(Resource Acquisition Is Initialization)?

RAII指的就是资源分配即初始化,它的作用就是定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。它是一种技术。

  • 为什么要使用RAII?

在堆上分配空间时,我们必须很仔细的申请并给出相应的释放语句,但是随着程序的复杂度增大,判断、循环、递归这样的语句会让程序走向不确定,很有可能出现申请了没释放,申请了多次释放。所以我们定义了一个类来封装资源的分配和释放。

  • 那么什么叫智能指针呢?

智能指针是利用了RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。

  • 智能指针的分类:

1、AutoPtr     (在函数库中都是小写加下划线,比如AutoPtr 函数库中为auto_ptr)

template <typename T>

class AutoPtr
{
public:
	AutoPtr(T* ptr=NULL):_ptr(ptr){}
	AutoPtr(AutoPtr<T>& t)
	{
	    _ptr=t._ptr;
	    t._ptr=NULL;
	}
	AutoPtr<T>& operator=(AutoPtr<T>& t)
	{
		if(_ptr!=t._ptr)
		{
			if(_ptr){	delete _ptr;	}
			_ptr=t._ptr;
			t._ptr=NULL;
		}
		return *this;
	}
	T& operator*(){	return *_ptr;}
	T* operator->(){return _ptr;}

	~AutoPtr(){ if(_ptr)	{      delete _ptr;     }  }
private:
	T* _ptr;
};

AutoPtr可以new出空间后,不必delete,出了作用域后会自动释放。

表面上这看似完美,可现实并不是这样。它无法像指针那样同一块空间被多个指针指向,它只能有一个指针指向一块空间,当发生拷贝构造或者赋值运算符重载时,它会释放原先的指针。

2、ScopedPtr

ScopedPtr实际上就是把AutoPtr的拷贝构造和赋值运算符的重载写成私有的,不让用户访问,这样就不会出现同一块空间被多个指针指向,但是这毕竟是治标不治本。

3、SharedPtr

template <class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr=NULL)
		:_ptr(ptr)
		,_pcount(new int(1))
	{}
	SharedPtr( SharedPtr<T>& t)
	{
		_ptr=t._ptr;
		_pcount=t._pcount;
		(*_pcount)++;
	}
	SharedPtr<T>& operator=(SharedPtr<T>& t)
	{
		if(_ptr!=t._ptr)
		{
			if(--(*_pcount)==0)
			{
				delete _ptr;
				delete _pcount;
			}
			_ptr=t._ptr;
			_pcount=t._pcount;
			++(*p_count);
		}
		return *this;
	}
	T& operator*(){	return *_ptr;	}
	T* operator->(){	return _ptr;	}
	~SharedPtr()
	{
		if(--(*_pcount)==0)
		{
			delete _ptr;
			delete _pcount;
		}
	}
private:
	T* _ptr;
	int* _pcount;   
};

为什么_pcount的类型是int*类型?

如果_pcount是int类型的,那么构造函数、拷贝构造函数和赋值运算符重载的代码应为:

SharedPtr(T* ptr = NULL):_ptr(ptr), _pcount(1){}
SharedPtr(SharedPtr<T>& t)
{
_ptr = t._ptr;
_pcount = t._pcount;
(_pcount)++;
}
SharedPtr<T>& operator=(SharedPtr<T>& t)
{
    if (_ptr != t._ptr)
    {
	if (--(_pcount) == 0)
	{
	    delete _ptr;
	 }
	_ptr = t._ptr;
	_pcount = t._pcount;
	++(_pcount);
    }
    return *this;
   }
SharedPtr<int> s = new int(1);          //引用计数为1
SharedPtr<int> s1(s);                //引用计数为2(s的引用计数仍为1)
SharedPtr<int> s2=new int (2);
s=s2;
cout<<*s1<<endl;  //正确输出为1,可是输出的却是一个随机值,因为s的引用计数始终为1,当运行到s=s3时,s在堆上分配的空间已经被释放了,所以s1指向的是被释放后的空间。

int类型有几个指针就会在栈上开辟几个_pcount。而int*类型时,只在堆上开辟1块空间来保存*_pcount的值,拷贝构造和赋值时都用的_pcount的地址取值后进行加减。

那为什么不用static int类型的呢?
看下面的例子:

SharedPtr<int> s1 = new int(1);             
SharedPtr<int> s2(s1); 
SharedPtr<int> s3 = new int(1);             
SharedPtr<int> s4(s3);

那么此时的引用计数变为了4,应为_pcount现在是所有对象共享的,定义一个对象就会+1,而我们的本意是让s1和s2共享一个_pcount,s3和s4共享一个_pcount。而int*可以做到这一点,当构造s1时申请一块空间实现引用计数,构造s2时引用计数+1。构造s3时再申请一块空间实现引用计数,构造s4时引用计数+1;

SharedPtr实现了引用计数,它支持复制,复制一个SharedPtr的本质是对这个智能指针的引用次数加1,而当这个智能指针的引用次数降低到0的时候,该对象自动被析构。

这样的SharedPtr依然存在着问题。

①如果shared_ptr引用关系中出现一个环,那么环上所述对象的引用次数都肯定不可能减为0,那么也就不会被删除。

struct ListNode
{
    int _value;
    SharedPtr<ListNode> _next;
    SharedPtr<ListNode> _prev;
    ListNode(int x):_value(x),_next(NULL),_prev(NULL){}
};
void Test()
{
    SharedPtr<Node> cur(new Node(1));
    SharedPtr<Node> next(new Node(2));
    cur -> _next = next;
    next -> _prev = cur;
}

上述例子中的对象引用计数不会减为0,所以不会调用析构,会造成内存泄漏。

实际上,在库函数中shared_ptr内部实现的时候维护的就不是一个引用计数,而是两个引用计数,一个表示strong reference,也就是用shared_ptr进行复制的时候进行的计数,一个是weak reference,也就是用weak_ptr进行复制的时候的计数。weak_ptr本身并不会增加strong reference的值,而strong reference降低到0,对象被自动析构,weak_ptr辅助了shared_ptr而没有增加引用计数。因此在一个环上只要把原来的某一个shared_ptr改成weak_ptr,实质上这个环就可以被打破了。

②模拟实现的SharedPtr只能用于new空间,并不能打开文件,这个时候可以用仿函数来解决这个问题。

template <class T>
struct FClose                    
{
     void operator () (T* ptr)           //重载()运算符,进行文件指针的释放。
    {
         fclose(ptr);
    }
};
template <class T>
struct Delete
{
public :
     void operator () (T* ptr)          //重载()运算符,进行堆上空间的释放
    {
         delete ptr;
    }
};
template <class T,class DEL=Delete<T>>   //多传一个参数,默认为Delete<T>,即默认它是在堆上new出空间,需要用delete释放
class SharedPtr
{
public:
	SharedPtr(T* ptr=NULL)
		:_ptr(ptr)
		,_pcount(new int(1))
	{}
	SharedPtr( SharedPtr<T>& t)
	{
		_ptr=t._ptr;
		_pcount=t._pcount;
		(*_pcount)++;
	}
	SharedPtr<T>& operator=(SharedPtr<T>& t)
	{
		if(_ptr!=t._ptr)
		{
			if(--(*_pcount)==0)
			{
				delete _ptr;
				delete _pcount;
			}
			_ptr=t._ptr;
			_pcount=t._pcount;
			++(*p_count);
		}
		return *this;
	}
	T& operator*(){	return *_ptr;	}
	T* operator->(){	return _ptr;	}
	~SharedPtr()
	{
		if(--(*_pcount)==0)
		{
			DEL()(_ptr);      //释放空间时,用DEL类型生成匿名对象调用()函数
			delete _pcount;
		}
	}
private:
	T* _ptr;
	int* _pcount;
};

当需要给文件指针定义时,只用多传一个参数就可以达到效果。

SharedPtr<FILE, FClose<FILE>> b= fopen("test.txt", "w");这样在析构时,就会调用FClose<FILE>()生成匿名对象,FClose<FILE>()(_ptr)然后调用FClose的()运算符重载函数。

时间: 2024-12-30 08:04:21

RAII和模拟实现智能指针的相关文章

智能指针(模拟实现AutoPtr、ScopedPtr、SharedPtr)

模拟实现AutoPtr.ScopedPtr.SharedPtr 智能指针实际上就是能够智能化的管理动态开辟空间的内存释放问题,C++中引入智能指针,很大一方面是当我们在动态开辟空间时,由于一些疏忽,或者说是对于一些代码,执行的顺序不是我们预期能够想到的,导致一些内存泄露的问题,使得程序健壮性不够,可维护性降低. 智能指针的基本特点: 1)智能指针管理的是一块内存的释放. 2)智能指针是一个类,有类似指针的功能. 下面主要是AutoPtr的理解: 当我们了解上面的知识后,模拟实现智能指针AutoP

C++ 资源管理(RAII)--智能指针

1. 智能指针(Smart Pointer) i. 是存储指向动态分配(堆)对象指针的类 ii. 在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象 iii. RAII类模拟智能指针,见备注 2. C++11提供了以下几种智能指针,位于头文件<memory>,它们都是模板类 i. std::auto_ptr(复制/赋值) ii. std::unique_ptr  c++11 iii.std::shared_ptr  c++11 iv.std::weak_ptr    c++11

使用智能指针来管理对象 (基于RAII)

////一个简单的防止内存泄露的例子//void test() { //使用RAII的特性管理资源 //当智能指针unique_ptr被销毁时,它指向的对象也将被销毁 //这里test函数返回后 p将自动销毁 //unique_ptr<int[]> p( new int[200] ); //直接生成资源 //test函数返回后 p不能被正常销毁,就会造成资源泄露 //int* p = new int[200]; } int main() { while( 1 ) { test(); Sleep

浅谈RAII&智能指针

关于RAII,官方给出的解释是这样的"资源获取就是初始化".听起来貌似不是很懂的哈,其实说的通俗点的话就是它是一种管理资源,避免内存泄漏的一种方法.它可以保证在各种情况下,当你对对象进行使用时先通过构造函数来进行资源的分配和初始化,最后通过析构函数来进行清理,有效的保证了资源的正确分配和释放.(特别是在异常中,因为异常往往会改变代码正确的执行顺序,这就很容易引起资源管理的混乱和内存的泄漏) 其中智能指针就是RAII的一种实现模式,所谓的智能就是它可以自动化的来管理它所指向那份空间的资源

智能指针的模拟实现 auto_ptr scoped_ptr shared_ptr

RAII(Resource Acquisition Is Initialization) 资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放. 智能指针:用自动化或者说智能的指针来实现对动态内存的释放.它是一个类,有类似指针的功能. 常见的智能指针:auto_ptr/scoped_ptr/scoped_array/shared_ptr/shared_array,由于scoped_array和scoped_

RAII&智能指针

智能指针是C++中为了实现资源的有效管理而被提出的,我们可以创建它但无须操心它的释放问题,在引入异常机制的程序里它是十分有用的,或者说,对于博主这中粗心大意的人来说还是可以偶尔使用的.他可以在一些场合防止内存泄漏的问题.但是,智能指针也是存在着许多的问题,所以许多的编程规范里告诫我们少使用智能指针,但对于我们来说,必须了解它的原理. *RAII:资源获得即初始化,我们在构造函数里将其初始化,并在析构函数里释放它 eg:一个简单的AutoPtr的实现 template<class T> clas

智能指针:模拟实现auto_ptr,scoped_ptr,shared_ptr

RAII(Resource Acquisition Is Initialization) 资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放. 所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放. STL--auto_ptr Boost库的智能指针(ps:新的C++11标准中已经引入了unique_ptr/shared_ptr/weak_ptr) 常见的智能指针有:auto_ptr/scoped

【C++】智能指针的作用,模拟实现auto_ptr,scoped_ptr,shared_ptr

RAII(Resource Acquisition Is Initialization): 资源分配即初始化,定义封装一个类,用来实现调用构造函数时就可完成资源的分配和初始化,在调用析构函数就可完成资源的清理,以实现对资源的初始化和清理. 智能指针: 用自动化或者说智能的指针来实现对动态内存的释放. 它是一个类,有类似指针的功能. 常见的智能指针有:auto_ptr/scoped_ptr/scoped_array/shared_ptr/shared_array,我们今天先讲以下三种. 一.Aut

C++的RAII和智能指针小结

RAII:资源分配即初始化,利用构造函数和析构函数定义一个类来完成对资源的分配和释放 智能指针主要用来防止内存泄漏,我们来举个栗子,看看为什么会有智能指针这个东东 例1: 对于上面这段程序,由于抛出异常的时候影响了代码的执行流,所以要在异常捕获之前将p提前释放(详见 我的博客:C++的异常浅析),虽然可以通过再次抛出以异常解决这个问题,但是在代码美观方面不够完 美,更重要的是如果在代码量非常大,而且在多处有动态开辟出来的空间的时候,仅仅通过再次抛出异常已 经远远不够解决这个问题了,会使得工作量大