什么是智能指针
智能指针是一个管理指针的类。用来存储指向动态分配对象(在堆区 new 出来的)的指针。在它的生命周期结束时,它可以负责自动释放动态分配的对象,调用析构函数,以达到防止堆内存泄露的目的。
智能指针发展史
- C++98 引入 auto_ptr (已废弃)
- C++11 引入 unique_ptr 和 shared_ptr
原理
RAII
RAII 代表 resource acquisition is initialization 的缩写,意为“资源获取即初始化”。它是 C++ 之父提出的设计理念,核心是把资源和对象的生命周期绑定,对象创建获取资源,销毁释放资源。这样做有两大好处:
- 不需要显式的释放资源。
- 对象所需资源在其生命周期始终保持有效。
实现一个简单的智能指针:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <iostream> using namespace std;
template<class T> class smartPtr { public: smartPtr(T* ptr = nullptr) : ptr_(ptr) {} ~smartPtr() { if (ptr_) { delete ptr_; } cout << "~smartPtr called!" << endl; } private: T* ptr_; }; int main() { int* a = new int(1); smartPtr<int> sp(a); smartPtr<int>sp2(new int(2)); }
|
上面的还不足以称为智能指针,因为
- 没有重载运算符 * 和 ->。
- 如果使用了拷贝或者赋值操作,由于是浅拷贝,会有 double free 的错误。
1 2 3 4 5
| int main() { smartPtr<Date> sp(new Date); smartPtr<Date> sp2(sp); return 0; }
|
auto_ptr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> #include <memory> using namespace std; class Date { public: Date(): year_(0), month_(0), day_(0) {} ~Date(){} int year_; int month_; int day_; };
int main() { auto_ptr<Date> ap(new Date); auto_ptr<Date> copy(ap); ap->year_ = 2022; }
|
可以发现报错了。原因是对于 auto_ptr 来说,赋值后之前的指针就被置空了。在拷贝或者赋值的过程中,auto_ptr 会传递所有权,将资源全部从源指针转移给目标指针,源指针被置空。虽然这种方法确实解决了浅拷贝的问题,但是我们使用auto_ptr的时候要注意,不要对源指针进行访问或者操作。
auto_ptr 实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| template<class T> class auto_ptr { public: auto_ptr(T* ptr = nullptr) : ptr_(ptr) {} auto_ptr(auto_ptr<T>& ap) : _ptr(ap.ptr_) { ap.ptr_ = nullptr; }
auto_ptr<T>& operator = (auto_ptr<T>& ap) { if (this != *ap) { delete ptr_; ptr_ = ap.ptr_; ap.ptr_ = nullptr; } return *this; }
~auto_ptr() { if (ptr_) { delete ptr_; } } T& operator *() { return *ptr_; } T* operator ->() { return ptr_; } private: T* ptr_; };
|
unique_ptr
相比 auto_ptr,unique_ptr 得不到就毁掉,不允许别人拿到。禁用拷贝和赋值构造函数。只能采用移动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| template<class T> class unique_ptr { public: unique_ptr(T* ptr = nullptr) : ptr_(ptr) {} unique_ptr(unique_ptr<T>& ap) = delete; unique_ptr<T>& operator = (unique_ptr<T>& ap) = delete;
~unique_ptr() { if (ptr_) { delete ptr_; } } T& operator *() { return *ptr_; } T* operator ->() { return ptr_; } private: T* ptr_; };
|
shared_ptr
shared_ptr 使用最为广泛,可以提供安全的拷贝构造。
原理:
对一个资源加一个计数器,让所有管理资源的 shared_ptr 共用这个计数器。如果发生拷贝,计数器 +1。等于 0 的时候,就析构。
再具体一点:
- shared_ptr 在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象析构函数调用时,对象的引用计数减一。
- 如果引用计数是 0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是 0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
模拟实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| template<class T> class shared_ptr { public: shared_ptr(T* ptr =nullptr) : ptr_(ptr), pcount_(new int(1)) {} shared_ptr(const T& sp) ptr_(sp.ptr_), pcount_(sp.pcount_) { ++(*pcount_); } shared_ptr<T>& operator = (shared_ptr<T>& sp) { if (ptr_ != sp.ptr_) { if (--(*pcount_) == 0) { delete pcount_; delete ptr_; } ptr_ = sp.ptr_; pcount_ = sp.pcount_; ++(*pcount_); } return *this; }
T& operator *() { return *ptr_; } T* operator ->() { return ptr_; }
~shared_ptr() { if (--(*pcount_) == 0 && ptr_) { delete pcount_; delete ptr_; } } private: T* ptr_; int* pcount_; };
|
拷贝构造图解:
多线程版
上述代码没有考虑多线程,下面有个带锁版本,仅供参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| template<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) : _ptr(ptr), _pcount(new int(1)), _pmtx(new mutex) {}
void add_ref() { _pmtx->lock(); ++(*_pcount); _pmtx->unlock(); }
void release_ref() { bool flag = false; _pmtx->lock(); if (--(*_pcount) == 0 && _ptr) { delete _pcount; delete _ptr; flag = true; cout << "释放资源:" << _ptr << endl; } _pmtx->unlock(); if (flag) { delete _pmtx; } }
shared_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr), _pcount(sp._pcount), _pmtx(sp._pmtx) { add_ref(); } shared_ptr<T>& operator = (const shared_ptr<T>& sp) { if (_ptr != sp._ptr) { if (--(*_pcount) == 0){ delete _pcount; delete _ptr; } _ptr = sp._ptr; _pcount = sp._pcount; add_ref(); } return *this; } T& operator *() { return *_ptr; }
T* operator ->() { return _ptr; }
T* get() { return _ptr; } int use_count() { return *_pcount; }
~shared_ptr() { release_ref(); } private: T* _ptr; int* _pcount; mutex* _pmtx; };
|
删除器
库中的shared_ptr,我们在析构的时候默认都是 delete _ptr,如果我们托管的类型是 new T[] ,或者 malloc 出来的话,就导致类型不是匹配的,无法析构。这个时候我们要自定义删除器,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <iostream> #include <memory> using namespace std; template<class T> struct DeleteArray { void operator()(T* ptr) { cout << "delete functor called!" << endl; delete[] ptr; } };
int main() { DeleteArray<string> delfunctor; std::shared_ptr<string>s2(new string[10], delfunctor); std::shared_ptr<string>s3((string*)malloc(sizeof(100)), [](string* ptr) { cout << "call free!" << endl; free(ptr); }); return 0; }
|
1 2 3
| @└────> # ./a.out call free! delete functor called!
|
weak_ptr
有以下情形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> #include <memory> using namespace std; struct ListNode { shared_ptr<ListNode> prev_; shared_ptr<ListNode> next_; int val_; ~ListNode() { cout << "~ListNode called!" << endl; } };
int main() { shared_ptr<ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode); n1->next_ = n2; n2->prev_ = n1; }
|
运行发现,析构函数没有被调用。
两边引用计数都是2,像是死锁一样,都不会析构。这时候就要用到 weak_ptr了。weak_ptr 是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,是为了解决循环引用而生的。我们只能使用 weak_ptr 或者 shared_ptr 去初始化它。
我们在会产生循环引用的位置,把shared_ptr 换成 weak_ptr。 weak_ptr 不是一个 RAII 智能指针,它不参与资源的管理,他是专门用来解决引用计数的,我们可以使用一个shared_ptr 来初始化一个 weak_ptr,但是 weak_ptr 不增加引用计数,不参与管理,但是也像指针一样访问修改资源。
上面代码修改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> #include <memory> using namespace std; struct ListNode { weak_ptr<ListNode> prev_; weak_ptr<ListNode> next_; int val_; ~ListNode() { cout << "~ListNode called!" << endl; } };
int main() { shared_ptr<ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode); n1->next_ = n2; n2->prev_ = n1; }
|
实现代码参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| template<class T> class weak_ptr { public: weak_ptr() : _ptr(nullptr) {} weak_ptr(shared_ptr<T>& sp) :_ptr(sp.get()), _pcount(sp.use_count()) {} weak_ptr(weak_ptr<T>& sp) : _ptr(sp._ptr), _pcount(sp._pcount) {} weak_ptr& operator = (shared_ptr<T>& sp) { _ptr = sp.get(); _pcount = sp.use_count(); return *this; } weak_ptr& operator = (weak_ptr<T>& sp) { _ptr = sp._ptr; _pcount = sp._pcount; return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } int use_count() { return *_pcount; } private: T* _ptr; int* _pcount; };
|