1、什么是RAII技术
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的简单计数。它定义了一个类来封装资源的分配和释放,在构造函数完成资源的分配和初
始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。这种做法有两大优点:
(1)不需要显示的释放资源。
(2)采用这种方式,对象所需的资源在其生命周期内始终保持有效。
2、为什么需要智能指针
代码中经常会忘记释放动态开辟的资源,这样就不知不觉的造成内存泄漏
3、什么是智能指针
所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放。
智能指针是RAII的一种应用,他可以:
(1)构造函数初始化,析构函数释放资源
(2)像指针一样使用——operator*/operator->/operator[]
(3)对各种类型智能指针赋值
4、智能指针的发展与实现:
当我们有了对象sp1,用它构造了sp2,有时我们希望它们拥有不同的内存空间,但有希望当其中一个值改变时另一个值也随即改变。此时深拷贝只帮我们实现了前半部分,
即一个对象一个内存空间,浅拷贝帮我们实现了后半部分,即改变其中一个值另一个值也随即改变,但它的内存释放有问题,因此我们需要另一种方法实现它。
(1)C++98/03 用的是Auto_ptr,它实现内存管理权的转移,这是一种有缺陷的方法;
代码示例:
#include<iostream>
#include<memory>
using namespace std;
template <class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
AutoPtr( AutoPtr<T>& a)
{
_ptr=a._ptr;
_ptr=NULL;//管理权转移
}
AutoPtr<T>& operator=(const AutoPtr<T>& a)
{
if(this!=&a)
{
delete this->_ptr;
this->_ptr=a._ptr;
a._ptr=NULL;
}
return *this;
}
~AutoPtr()
{
delete _ptr;
cout<<"~AutoPtr()"<<endl;
}
T& operator*()
{
if(_ptr==NULL)
{
cout<<"空指针"<<endl;
}
return *_ptr;
}
T& operator->()
{
if(_ptr==NULL)
{
cout<<"空指针"<<endl;
}
return _ptr;
}
private:
T* _ptr;
};
int main()
{
AutoPtr<int> sp1(new int(5));
AutoPtr<int> sp2(sp1);
*sp2=10;
return 0;
}
内存展示:
sp2是sp1拷贝构造的,会使sp2的对象指向sp1,即获得sp1的管理权,而sp1不再管理对象sp1,所以又造成上图所示的sp1的指针_ptr指向错误。
(2)介于Auto_ptr的缺陷,之后出现了scoped_ptr(boost库)和unique_ptr(C++11)。它们采用简单粗暴的方法—防拷贝,它将拷贝构造函数和赋值运算符重载函数只声明不定
义,并且声明成保护成员函数,这样就避免了管理权的转移,并防止了在类外定义这些函数。但也因此不能实现对象之间的拷贝和赋值,这也是ScopedPtr的不足之处。
测试代码如下:
#include<iostream>
#include<memory>
using namespace std;
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
delete _ptr;
cout<<"~ScopedPtr()"<<endl;
}
T&operator*()
{
return *_ptr;
}
T&operator->()
{
return _ptr;
}
protected:
ScopedPtr(ScopedPtr<T>& s);//防拷贝(只声明,为防止在内外定义)
ScopedPtr<T> operator=(ScopedPtr<T>& s);
private:
T* _ptr;
};
void TestScopedPtr()
{
ScopedPtr<int> sp1(new int(5));
/*ScopedPtr<int> sp2(sp1);*/ //不能访问protected成员
}
int main()
{
TestScopedPtr();
return 0;
}
(3)由于以上两种方法的缺陷,Shared_Ptr(boost/C++11)应运而生。这是一种比较完善得方法,他保持了对象的共享拥有权。若干个Shared_Ptr对象可以共享同一个对象,该对象通过维护一个引用计数,记录有多少个Shared_Ptr指针指向该对象,最后一个指针指向该对象的Shared_Ptr将销毁或重置,即引用计数减为0.销毁对象时使用的引用计数是delete表达式或者是构造Shared_Ptr是传入的自定义删除器,但是Shared_Ptr指针同样有缺陷,那就是循环引用和现成安全问题,不过可以采用weak_ptr配合解决。
Shared_Ptr实现代码(实现引用计数):
#include<iostream>
#include<memory>
using namespace std;
template<class T>
class Shared_Ptr
{
public:
Shared_Ptr(T* ptr)
:_ptr(ptr)
,_refcount(new int(1))
{}
Shared_Ptr()
:_ptr(NULL)
,_refcount(new int(1))
{}
Shared_Ptr(const Shared_Ptr<T>& sp)
:_ptr(sp._ptr)
,_refcount(sp._refcount)
{
(*_refcount)++;
}
Shared_Ptr<T>& operator=(const Shared_Ptr<T>&sp)
{
if(_ptr!=sp._ptr)
{
delete _ptr;
delete _refcount;
_ptr=sp._ptr;
_refcount=sp._refcount;
++(*_refcount);
}
return *this;
}
~Shared_Ptr()
{
Realease();
}
T& operator*()
{
return *_ptr;
}
T& operator->()
{
return _ptr;
}
T Getrefcount()
{
return*(_refcount);
}
inline void Realease()
{
if(--*_refcount==0)
{
delete _refcount;
delete _ptr;
}
}
void Reset(T* ptr,T* refcount)
{
if(_ptr!=ptr)
{
delete _ptr;
delete _refcount;
}
_ptr=ptr;
_refcount=refcount;
}
public:
T* _ptr;
T* _refcount;
};
void test()
{
Shared_Ptr<int> s1(new int(10));
cout<<s1.Getrefcount()<<endl;
Shared_Ptr<int> s2(s1);
cout<<s2.Getrefcount()<<endl;
Shared_Ptr<int> s3(new int(30));
s3=s1;
cout<<s3.Getrefcount()<<endl;
}
int main()
{
test();
return 0;
}
这种方法看似不错,但实际上存在以下问题:
(1)、引用计数更新存在线程安全;
(2)、循环引用;
(3)、定置删除器;
下面我们将使用一个弱指针(weak_ptr)来打破循环引用。
#include<boost/shared_ptr.hpp>
#include<boost/weak_ptr.hpp>
using namespace std;
using namespace boost;
struct ListNode
{
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
/*weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;*/
~ListNode()
{
cout<<"~ListNode"<<endl;
}
};
void test()
{
//循环引用问题
shared_ptr<ListNode>p1(new ListNode());
shared_ptr<ListNode>p2(new ListNode());
p1->_next=p2;//P1节点的_next指向p2节点
p2->_prev=p1;//p2节点的_prev指向p1节点
cout<<"p1->Count:"<<p1.use_count()<<endl;
cout<<"p2->Count:"<<p2.use_count()<<endl;
}
int main()
{
test();
return 0;
}
运行结果:
定置删除器和空间分配器:
class FClose
{
public:
void operator() (void* ptr)
{
cout<<"fclose"<<endl;
fclose((FILE*)ptr);
}
};
class Free
{
public:
void operator() (void* ptr)
{
cout<<"free"<<endl;
free(ptr);
}
};
void Test()
{
shared_ptr<FILE>p1 (fopen("test.txt","w"),FClose());//打开一个文本文件再手动删除
shared_ptr<int>p2 ((int*)malloc(sizeof(int)),Free(),allocator<int>());//手动申请空间并释放
}
int main()
{
Test();
return 0;
}
定置删除器进行手动的开辟和删除,人为解决空间的开辟和释放问题。
5、智能指针总结:
如果确定要使用智能指针的话,scoped_ptr完全可以胜任。在非常特殊的情况下,例如对STL容器中的对象,你应该使用std::tr1::shared_ptr,任何情况下都不要使用auto_ptr。
"智能"指针看上去是指针,其实是附加了语义的对象。以scoped_ptr为例,scoped_ptr被销毁时,删除了他所指向的对象。shared_ptr也是如此,而且,shared_ptr实现了
引用计数,从而只有当它所指向的最后一个对象被销毁时,指针才被删除。