目录
前言
1、为什么需要智能指针?
2、智能指针的原理
3、智能指针的分类
3.1 auto_ptr
3.2 unique_ptr
C++11中引入了智能指针的特性,本文将详细介绍智能指针的使用。
我们来看一段代码:
void Func()
{
int* p1 = new int;
int* p2 = new int;
cout << div() << endl;
delete p1;
delete p2;
}
在这段代码中,如果在div中抛异常,很显然会造成内存泄漏。为了避免内存泄漏,我们希望有一种指针可以做到自动管理内存释放。
RALL:RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
// 使用RAII思想设计的SmartPtr类
template
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;
};
智能指针的原理:
在c++的库中定义了几种不同的智能指针,头文件为memory。在智能指针进行拷贝构造或者赋值时,会出现两个指针指向同一块内存的情况,这样在释放空间时就会出现问题。针对这个问题有三种不同的解决方法,对应c++库中的三种指针,前两种都是不成熟的做法,简单了解即可。
C++98版本的库中就提供了auto_ptr的智能指针。
auto_ptr的实现原理:管理权转移的思想,简单的将原指针设为空,把管理权交给新指针。
auto_ptr(auto_ptr& sp)
:_ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}
但是这样会出现很大的问题,因为有时候指针的权限已经发生了转移,但是使用指针的人并不知道,很可能造成越界访问。
结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr。
unique_ptr的实现原理:简单粗暴的防拷贝。
unique_ptr(const unique_ptr& sp) = delete;
unique_ptr& operator=(const unique_ptr& sp) = delete;
这个指针时是不能拷贝的,所以在很多情况下不能使用。
C++11中开始提供更靠谱的并且支持拷贝的shared_ptr。shared_ptr的原理是通过引用计数的方来实现多个shared_ptr对象之间共享资源。
注意:
简单模拟实现如下:
template
class shared_ptr
{
public:
shared_ptr(T* ptr)
:_ptr(ptr)
, _pcount(new int(1))
, _pmutex(new mutex)
{}
~shared_ptr()
{
RealseRef();
}
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
, _pmutex(sp._pmutex)
{
AddRef();
}
shared_ptr& operator= (const shared_ptr&sp)
{
if (_ptr == sp._ptr)
{
return *this;
}
//先释放掉现在指向的资源
RealseRef();
_ptr = sp._ptr;
_pmutex = sp._pmutex;
_pcount = sp._pcount;
AddRef();
return *this;
}
private:
void AddRef()
{
_pmutex->lock();
(*_pcount)++;
_pmutex->unlock();
}
void RealseRef()
{
_pmutex->lock();
bool flag = false;
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
flag = true;
}
_pmutex->unlock();
if (flag == true)
{
delete _pmutex;
}
}
T* _ptr;
int* _pcount;
mutex* _pmutex;
};
shared_ptr的循环引用:
在某些情况下会使用到循环引用,这时候可能会出现问题。
struct ListNode
{
int _data;
shared_ptr _prev;
shared_ptr _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr node1(new ListNode);
shared_ptr node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
return 0;
}
智能指针指向的空间中也保存有智能指针,且两个智能指针指向的空间中保存的智能指针还指向对方的空间,这称为循环引用。
为了解决这个问题。C++中增加了weak_ptr。在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了。
struct ListNode
{
int _data;
weak_ptr _prev;
weak_ptr _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
weak_ptr只是单纯的赋值,不会使引用计数++,析构后也不负责释放空间。 weak_ptr就好比一个局外人,只起到索引的作用。
删除器:
析构智能指针时需要的操作并不相同,有时候传给智能指针的指针可能会一下new出来了多个值,这时候就需要delete[ ],或者传过去的指针是打开文件的指针,析构时不应该delete而是进行关闭文件操作。shared_ptr设计了一个删除器来解决这个问题。
del是一个可调用对象,通过传入del可以自定义析构智能指针时进行的操作。
template
struct DelArr
{
void operator()(const T* ptr)
{
cout << "delete[]:"< spArr(new ListNode[10], DelArr());
std::shared_ptr spfl(fopen("test.txt", "w"), [](FILE* ptr){
cout << "fclose:" << ptr << endl;
fclose(ptr);
});
}
总结
本文主要简单介绍了智能指针的使用,希望能给大家带来帮助。江湖路远,来日方长,我们下次见~