本文将解决一下几个问题
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个整数的空间