RAII:
1.RAII的由来:
我们写的有关资源管理的代码如果出现bug,会导致下面的问题:
●malloc出来的空间,没有进行释放,存在内存泄漏的问题。
●异常安全问题。如果在malloc和free之间如果存在抛异常,那么还是有内存泄漏。这种问题就叫异常安全。
也许会有人说,那我写代码的时候小心点就行了,但是天算不如人算,当工程量浩大的时候总避免不了这样那样的问题,因此就出现了一种叫做RAII的技术。
2.什么是RAII?
RAII是一种利用对象生命周期来控制程序资源(如内存,互斥量)的技术。
RAII思想:1>用一个类封装资源的分配和释放,把管理一份资源的责任托管给了一个对象。
2>在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效。
3>最后在对象析构的时候释放资源。
template<class T>
class RAII
{
public:
RAII(T* ptr=nullptr)
:_ptr(ptr)
{}
~RAII()
{
if (_ptr)
delete_ptr;
}
private:
T* _ptr;
};
int main()
{
try
{
int* p = (int*)malloc(sizeof(int));
RAII<int> sp(p);
//.....sp处理事务
//........
}
catch(const exception& e)
{
cout << e.what() << endl;
}
system("pause");
return 0;
}
好处:
●不需要显式地释放资源。即使我们忘记释放malloc出来的空间或者由于抛异常而导致空间没有释放,RAII对象都会帮你的,对象出了作用域自动调用析构函数,资源被释放。
●采用这种方式,对象所需的资源在其生命期内始终保持有效。
智能指针:
一、智能指针的特点
●具有RAII特性。
●具有像指针一样的行为,重载operator*和opertaor->。
使用智能指针,可以实现自动的内存管理,不需要担心忘记delete造成的内存泄漏。
二、智能指针分类
auto_ptr、unique_ptr、shared_ptr、weak_ptr
三、各种智能指针的原理及实现
1.auto_ptr:
1>实现原理:
管理权转移的思想,当把我的资源交给你后,我就置空,也就是说任何时候只能有一个人管理这块资源。这样其实是对的,如果我不置空,那么我们两个同时管理这块资源,那析构的时候资源释放两次岂不是有问题。
2>使用:
struct Date
{
int _year;
int _month;
int _day;
};
void test()
{
auto_ptr<Date> s1(new Date);
s1->_year = 1998;
s1->_month = 6;
s1->_day = 30;
auto_ptr<Date> s2(s1);//导致s1悬空
s2->_year = 2019;
s2->_month = 2;
s2->_day = 17;
//s1->_day = 1998;程序崩溃
}
3>缺点:
auto_ptr导致原对象悬空,这正是它的缺点,s1的资源已经被释放,但是很容易让人误用s1的资源,就会造成程序崩溃。
4>模拟实现:
template<class T>
class Auto_Ptr
{
public:
Auto_Ptr(T* ptr)
:_ptr(ptr)
{}
Auto_Ptr(Auto_Ptr<T>& copy)
:_ptr(copy._ptr)
{
copy._ptr = nullptr;
}
Auto_Ptr<T>& operator=(Auto_Ptr<T>& copy)
{
if (this != copy)
{
//释放当前对象资源
delete _ptr;
//转移资源
_ptr = copy._ptr;
copy->_ptr = nullptr;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~Auto_Ptr()
{
if (_ptr)
delete _ptr;
}
private:
T* _ptr;
};
2.unique_ptr
1>原理:简单粗暴,不让拷贝和赋值。既然auto_ptr拷贝对象会导致悬空问题,那我就不让你拷贝,这样是不是安全多了。
2>使用:
void test()
{
Unique_Ptr<Date> s1(new Date);
s1->_year = 1998;
s1->_month = 6;
s1->_day = 30;
//unique_Ptr<Date> s2(s1);编译不通过
}
3>实现:
template<class T>
class Unique_Ptr
{
public:
Unique_Ptr(T* ptr)
:_ptr(ptr)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~Unique_Ptr()
{
if (_ptr)
delete _ptr;
}
private:
//C++98防拷贝
/*Unique_Ptr(const Unique_Ptr<T>& );
Unique_Ptr<T>& operator=(const Unique_Ptr<T>&);*/
//C++11
Unique_Ptr(const Unique_Ptr<T>&) = delete;
Unique_Ptr<T>& operator=(const Unique_Ptr<T>&) = delete;
T* _ptr;
};
3.shared_ptr:
1>原理:通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
2>使用:
void test()
{
//引用计数
shared_ptr<Date> s1(new Date);
s1->_year = 2019;
s1->_month = 2;
s1->_day = 18;
shared_ptr<Date> s2(s1);
cout << "s1_use" << s1.use_count() << endl;//2
shared_ptr<Date> s3(s1);
cout << "s1_use" << s1.use_count() << endl;//3
cout << "s3_use" << s2.use_count() << endl;//3
cout << "s3_use" << s3.use_count() << endl;//3
}
3.模拟实现:
template<class T>
class Shared_Ptr
{
public:
Shared_Ptr(T* ptr = nullptr)
:_ptr(ptr)
,_count(new int(1))
, _mutex(new mutex)
{
if (_ptr == nullptr)
*_count=0;
}
Shared_Ptr(const Shared_Ptr<T>& copy)
:_ptr(copy._ptr)
,_count(copy._count)
,_mutex(copy._mutex)
{
//ptr不为空增加引用计数
if (_ptr)
AddCount();
}
Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp)
{
if (this != &sp)
{
//释放当前对象资源
Release();
//赋值
_ptr = sp._ptr;
_count = sp._count;
_mutex = sp._mutex;
if (_ptr)
AddCount();
}
return *this;
}
~Shared_Ptr()
{
Release();
}
int AddCount()
{
//原子操作
_mutex->lock();
(*_count)++;
_mutex->unlock();
return *_count;
}
int SubCount()
{
_mutex->lock();
(*_count)--;
_mutex->unlock();
return *_count;
}
T& UseCount()
{
return *_count;
}
T* Get()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
void Release()
{
if (_ptr == nullptr)
delete _mutex;
//引用即使减到零则释放资源
if (_ptr&& SubCount() == 0)
{
delete _ptr;
delete _count;
delete _mutex;
}
}
private:
T* _ptr;
int* _count;//引用计数
mutex* _mutex;//互斥锁
};
这种方法可以很好的实现拷贝构造,也不会造成前几种指针的问题。堪称完美,但是在某些方面依然有缺陷。比如:
struct ListNode
{
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode()
{
cout << "~ListNode" << endl;
}
};
int main()
{
shared_ptr<ListNode> s1(new ListNode);
shared_ptr<ListNode> s2(new ListNode);
cout << "s1 " << s1.use_count() << endl;
cout << "s2 " << s2.use_count() << endl;//1
s1->_next = s2;
s2->_prev = s1;
cout << "s1 " << s1.use_count() << endl;
cout << "s2 " << s2.use_count() << endl;//2
//出了main后 s1s2析构,但是s1->next和s2->prev还存在,这样谁都不会释放
}
上面的问题就叫做循环引用,也就是s1 s2绑在了一起,都等着对方释放,但是他们两个谁也不释放。
4.weak_ptr:专门为了收拾shared_ptr造成的循环引用问题。
1>原理:node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
2>使用:上面的问题使用weak_ptr就没有任何问题了。
//weak_ptr解决
struct ListNode
{
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode()
{
cout << "~ListNode" << endl;
}
};
void test()
{
shared_ptr<ListNode> s1(new ListNode);
shared_ptr<ListNode> s2(new ListNode);
s1->_next = s2;
s2->_prev = s1;
cout << "s1 " << s1.use_count() << endl;//1
cout << "s2 " << s2.use_count() << endl;//1
}
3>实现:
template<class T>
class WeakPtr
{
public:
WeakPtr(SharedPtr<T>& sp)//匿名对象
:_ptr(sp._ptr)//将sharedptr的对象给weakptr(弱指针)进行初始化,weakptr指向sharedptr的对象
{}
WeakPtr()
:_ptr(NULL)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
总结:每种智能指针都有自己的特点,但使用哪一种还是要具体情况具体分析。