什么叫RAII(Resource Acquisition Is Initialization)?
为什么要使用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;
};
当需要给文件指针定义时,只用多传一个参数就可以达到效果。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。