为什么需要智能指针
- malloc出来没有free,出现内存泄漏;
- 在free之前抛出异常,没有free,出现内存泄漏,这是异常安全的问题。
智能指针的使用与原理
RAII
RAII是一种利用对象生命周期来控制程序资源的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终有效,最后在对象析构时释放资源。
相当于把管理资源的责任委托给了一个对象,有两大好处:
- 不需要显示的释放资源;
- 采用这种方式,对象所需的资源在其生命周期内始终有效。
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr = nullptr)
:_ptr(ptr)
{};
~SmartPtr()
{
if(_ptr)
{
delete _ptr;
}
}
private:
T* _ptr;
};
void MergeSort(int* a,int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
SmartPtr<int> sp(tmp);
vector<int> v(100000000,20);
}
int main()
{
try{
int a[5] = {4,1,2,3,5};
MergeSort(a,5);
}
catch(const execption& e)
{
cout << e.what() <<endl;
}
return 0;
}
原理
上述不能称之为智能指针,他还不具备指针的行为,指针可以解引用,可以通过“->”指向所指内容,所以要将“*”、“->”重载。
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr = nullptr)
:_ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
struct Date
{
int year;
int month;
int day;
};
int main()
{
SmartPtr<int> sp(new int);
*sp = 10;
cout << *sp << endl;
SmartPtr<int> sd(new Date);
sd->year = 2019;
sd->month = 8;
sd->day = 13;
}
std::auto_ptr
C++库中提供了auto_ptr的智能指针
#include <memory>
class Date
{
public:
Date()
{
cout << "Date()" <<endl;
}
~Date()
{
cout << "~Date()" << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
auto_ptr<Date> ap(new Date);
auto_ptr<Date> copy(ap);
ap->_year = 2019;
return 0;
}
缺点:当对象拷贝或者赋值后,前面的对象就悬空了。
实现原理:管理权转移的思想。
std::unique_ptr
C++库中提供了更靠谱的unique_ptr指针
int main()
{
unique_ptr<Date> up(new Date);
unique_ptr<Date> copy(new Date);
return 0;
}
设计思路是防拷贝,不让拷贝与赋值。
std::shared_ptr
int main()
{
shared_ptr<Date> sp(new Date);
shared_ptr<Date> copy(sp);
cout << "count" << sp.use_count() <<endl;
cout << "count" << copy.use_count() <<endl;
}
原理:通过引用计数的方式实现多个shared_ptr对象之间共享资源。
- shared_ptr在其内部,给每一份资源都维护了一份计数,用来记录该份资源被几个对象共享;
- 在对象被销毁时,计数减1;
- 如果计数为0,要释放该资源;
- 如果不为0,说明还有对象使用该资源,若释放其他对象将指向野指针。
shared_ptr的线程安全问题
- 需要用锁,shared_ptr中计数++,不是原子性的,可能导致计数错乱,会导致资源未释放或程序崩溃的问题。
- 智能指针管理的对象存放在堆上,两个线程同时访问,易导致线程安全问题。
#include <thread>
#include <mutex>
#include <iostream>
using namespace std;
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = nullptr)
:_ptr(ptr)
, _pRefCount(new int(1))
, _pMutex(new mutex)
{
}
~SharedPtr()
{
Release();
}
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pMutex(sp._pMutex)
{
AddRefCount();
}
SharedPtr<T>& operator= (const SharedPtr<T>& sp)
{
if (_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pMutex = sp._pMutex;
AddRefCount();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int UseCount()
{
return *_pRefCount;
}
T* Get()
{
return _ptr;
}
void AddRefCount()
{
_pMutex->lock();
++(*_pRefCount);
_pMutex->unlock();
}
private:
void Release()
{
bool flag = false;
_pMutex->lock();
if (--(*_pRefCount) == 0)
{
delete _ptr;
delete _pRefCount;
flag = true;
}
_pMutex->unlock();
if (flag == true)
{
delete _pMutex;
}
}
private:
T* _ptr;
int* _pRefCount;
mutex* _pMutex;
};
struct Date
{
int _year;
int _month;
int _day;
};
//将AddRefCount与SubRefCount去掉;
//线程安全是偶然性出现的,n越大出现的概率越大;
void SharePtrFunc(SharedPtr<Date>& sp, int n)
{
cout << sp.Get() << endl;
for (size_t i = 0; i < n; i++)
{
SharedPtr<Date> copy(sp);
copy->_year++;
copy->_month++;
copy->_day++;
}
}
int main()
{
SharedPtr<Date> p(new Date);
cout << p.Get() << endl;
const size_t n = 100000;
thread t1(SharePtrFunc, p, n);
thread t2(SharePtrFunc, p, n);
t1.join();
t2.join();
cout << p->_year << endl;
cout << p->_month << endl;
cout << p->_day << endl;
system("pause");
return 0;
}
std::shared_ptr循环引用
#include <iostream>
#include <thread>
#include <memory>
using namespace std;
class ListNode
{
public:
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
system("pause");
return 0;
}
循环引用:
node1和node2两个智能指针对象指向两个节点,计数为1,不需要手动释放。;
node1的_next指向node2,node2的_prev指向node1,计数为2;
node1与node2析构,计数减为1,但_next与_prev分别指向下一个节点与上一个节点不变;即_next析构了,node2就释放了;_prev析构,node1就释放了;
_next属于node1成员,node1释放了_next才会析构,而node1由_prev管理,_prev属于node2成员,这就叫循环引用。
解决方案
在引用计数的场景中,把节点中的_prev与_next改为weak_ptr
原理:node1->_next = node2;node2->_prev = node1时weak_ptr不会增加node1与node2的引用计数。
#include <iostream>
#include <memory>
using namespace std;
class ListNode
{
public:
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
system("pause");
return 0;
}
若不是new出来的对象,如何通过智能指针管理呢?
shared_ptr中设计了一个删除器。
#include <iostream>
#include <memory>
using namespace std;
template<class T>
struct FreeFunc
{
void operator()(T* ptr)
{
cout << "free" << ptr << endl;
free(ptr);
}
};
template<class T>
struct DeleteArrayFunc
{
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
int main()
{
FreeFunc<int> freefunc;
shared_ptr<int> sp1((int*)malloc(4), freefunc);
DeleteArrayFunc<int> deletearrayfunc;
shared_ptr<int> sp2((int*)malloc(4), deletearrayfunc);
system("pause");
return 0;
}
RAII的其他作用
1.设计智能指针
2.设计守卫锁
3.防止异常安全导致的死锁问题。