本文将解决一下几个问题
1.什么是智能指针
2.为什么需要之智能指针
3.智能指针的使用场景
智能指针
RAII:是一种利用对象声明周期来控制的程序资源(如内存、文件句柄、网络连接、互斥量等)的技术
在对象构造的时候获取资源,接着控制资源的访问使之在对象的声明周期内始终有效,最后在对象析构的时候释放资源。这样做有2个好处
- 不需要显示的释放资源
- 对象所需要的资源在其声明周期有效,出声明周期会自动释放
为什么需要智能指针
C++中申请内存一般都是用new来申请,用delete 来释放对应的内存(资源)。内存的正确申请和释放其实很容易出现问题的,比如有时忘记了释放,或者申请了new[],但是直接delete,而不是delete [],如果是内置类型,如果有的话(什么都不做),不会出现报错,但是如果是内置类型就会出现内存泄露或者其他未定义的行为。 而这时智能指针出现了。
所谓智能指针其实就是类模版,它可以创建任意类型的指针(在创建的时候调用构造函数),在对象的声明周期结束的之前,会去调用自身的析构函数释所指向的空间。
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
if(ptr != nullptr)
{
delete ptr;
ptr = nullptr;
}
}
T& operator*()
{
return *ptr;
}
T* operator->()
{
return ptr;
}
private:
T* _ptr;
};
输出结果:
0x7ffd92fe90d0
0x7ffd92fe90e0
C++98版本的库中提供了auto_ptr的智能指针。
auto_ptr
auto的实现原理:管理权转移的思想,下面是一个简单版本的auto_ptr
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
// 检测是否为自己给自己赋值
if (this != &ap)
{
// 释放当前对象中资源
if (_ptr)
delete _ptr;
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
这里有一个很严重的问题,就是sp4拷贝构造sp3的时候,就会导致sp3指针悬空。
unique_ptr
所以后来C++11开始提供了unique_ptr
unique_ptr实现原理:就是防止拷贝,下面是一份模拟简化的unique_ptr
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
T* _ptr;
};
这时如果再继续上面的代码,就会直接报错,不会导致用户如果不知道内部实现可能会踩坑的情况。
shared_ptr
后来C++11中开始提供了更靠谱的并且支持拷贝的shared_ptr
shared_ptr实现原理:通过引用计数来实现多个shared_ptr对象之间共享资源。
1.其中shared_ptr在其内部,给每一个资源都维护了一份计数,用来记录该资源被多少个对象共享
2.在对象被销毁的时候,如果自己不再使用该资源了,引用计数会减少一。
3.如果引用计数是0,那么就说明是最后一个使用该资源的对象,必须释放资源。
4.如果不是0,就说明还有其他对象使用该资源,不用释放,否则其他对象就是野指针了。
下面是一份简化的模拟实现shared_ptr
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr)
:_ptr(ptr)
, _pcount(new int(1))
, _pmtx(new mutex)
{}
~shared_ptr()
{
Release();
}
void Release()
{
_pmtx->lock();
if (--(*_pcount) == 0)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _pcount;
// delete _pmtx; 如何解决?
}
_pmtx->unlock();
}
void AddCount()
{
_pmtx->lock();
++(*_pcount);
_pmtx->unlock();
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
, _pmtx(sp._pmtx)
{
AddCount();
}
// sp1 = sp4
// sp1 = sp1;
// sp1 = sp2;
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pcount = sp._pcount;
_pmtx = sp._pmtx;
AddCount();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
int use_count()
{
return *_pcount;
}
private:
T* _ptr;
int* _pcount;
mutex* _pmtx;
};
shared_ptr线程安全的问题
1.要注意的是shared_ptr对象中引用计数线程安全的,因为引用计数就是用锁来实现的。
2.shared_ptr本质还是在堆上申请释放的,如果两个线程去访问,就有可能出现线程安全的问题。
shared_ptr循环引用
shared_ptr中,循环引用是一个很大的问题。下面的代码中,node1有node2对象的资源,这样就会导致循环引用,因为node1和node2引用计数都是1,它们都不会自动释放掉资源,那么超出了声明周期,也不会调用析构函数去释放掉相对应的资源,从而导致了内存泄露。
struct ListNode
{
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;
return 0;
}
weak_ptr
weak_ptr:弱引用智能指针,用于解决shared_ptr的循环引用的问题。
weak_ptr可以观察shared_ptr的声明周期,但不会增加引用计数。
可以通过shared_ptr的成员函数weak_ptr::lock来获取一个指向同一个对象的shared_ptr.
std::weak_ptr<int> weakPtr = ptr1;
if(std::shared_ptr<int> sharedPtr = weakPtr.lock())
{
//使用shared_ptr
}
如果不是new出来的对象如何通过智能指针来管理呢?
C++中,智能指针(shared_ptr,unique_ptr)主要是用于管理动态申请的内存,即使用new关键字来管理内存,如果想用智能指针来管理不是new出来的对象,那么就可以尝试自定义删除器。
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(sizeof(int)), freeFunc);
DeleteArrayFunc<int> deleteArrayFunc;
shared_ptr<int[]> sp2((int*)malloc(10 * sizeof(int)), deleteArrayFunc); // 注意这里应该是int[],并且分配了10个整数的空间