本节分为五部分:
自己实现智能指针
不带引用计数的智能指针:auto_ptr 、scoped_ptr 、unique_ptr
带引用计数的智能指针:shared_ptr 、weak_ptr
多线程访问共享对象问题
自定义删除器
我们知道除了静态内存和栈内存外,每个程序还有一个内存池,这部分内存被称为自由空间或者堆。程序用堆来存储动态分配的对象即那些在程序运行时分配的对象,当动态对象不再使用时,我们的代码必须显式的销毁它们。
在 C++ 中,动态内存的管理是用一对运算符完成的:new 和 delete。
new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针;
delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。
动态内存管理经常会出现以下几种问题:
忘记释放资源,导致资源泄露(常发生内存泄漏问题)。
尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。
统一资源释放多次,导致释放野指针,程序崩溃。
代码的后面写了释放资源的代码,但是由于程序逻辑满足条件,从中间 return 掉了,导致释放资源的代码未被执行到。
代码运行过程中发生异常,随着异常栈展开,导致释放资源的代码未被执行到。
为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。
主要体现在用户可以不关注资源的释放,因为智能指针会帮你完全管理资源的释放,它会保证无论程序逻辑怎么跑,正常执行或者产生异常,资源在到期的情况下,一定会进行释放。
C++ 11 库中,提供了 带引用计数的智能指针和不带引用计数的智能指针 ,本章节主要以原理和应用场景。
1. 自己实现智能指针 智能指针的基本原理:
利用栈上的对象做出作用域会自动析构 的特点,把资源释放代码全部放在析构函数中执行,就达到了所谓的智能指针。
使用裸指针:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> using namespace std;int main () { int * p = new int ; delete p; return 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 #include <iostream> using namespace std;template <class T >class CSmartPtr {public : CSmartPtr (T* ptr = nullptr ) : mptr (ptr) { } ~CSmartPtr () { delete mptr; }private : T* mptr; };int main () { CSmartPtr<int > ptr (new int ) ; return 0 ; }
上面代码实现了较为简单的智能指针,主要用到两点:
(1)智能指针体现在把裸指针进行了一次面向对象的封装,在构造函数中初始化资源地址,在析构函数中负责释放资源。 (2)利用栈上的对象出作用域自动析构这一特点,在智能指针的析构函数中保证释放资源。
所以,智能指针一般都是定义在栈上的。
面试题:能不能在堆上定义智能指针?
1 CSmartPtr* p = new CSmartPtr (new int );
这里定义的 p 虽然是智能指针类型,但它实质上还是一个裸指针,因此 p 还是需要进行手动 delete,又回到了最开始裸指针的问题。
当然,智能指针要做到和裸指针相似,还得提供裸指针常见的 * 和->两种运算符的重载函数 ,使用起来才真正的和裸指针一样,代码扩充如下:
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 #include <iostream> using namespace std;template <class T >class CSmartPtr {public : CSmartPtr (T* ptr = nullptr ) : mptr (ptr) { } ~CSmartPtr () { delete mptr; } T& operator *() { return *mptr; } const T& operator *() const { return *mptr; } T& operator ->() { return *mptr; } const T& operator ->() const { return *mptr; }private : T* mptr; };int main () { CSmartPtr<int > ptr (new int ) ; *ptr = 20 ; cout << "ptr = " << *ptr << endl; return 0 ; }
上面的这个智能指针,使用起来就和普通的裸指针非常相似了,但是它还存在很大的问题,看下面的代码:
1 2 3 4 5 6 int main () { CSmartPtr<int > ptr1 (new int ) ; CSmartPtr<int > ptr2 (ptr1) ; return 0 ; }
这个 main 函数运行,代码直接崩溃,问题出在默认的拷贝构造函数做的浅拷贝,两个智能指针都持有一个 new int
资源,ptr2 仙溪沟释放了资源,到了 ptr1 析构的时候,就变成了 delete 野指针,造成程序的崩溃 。所以这里引出来智能指针需要解决的两件事情:
怎么解决智能指针的浅拷贝问题
多个智能指针指向同一个资源的时候,怎么保证资源只释放一次,而不是每个智能指针都释放一次,造成代码运行不可预期的严重后果
查看 C++ 库中的智能指针如何解决的问题。
2. 不带引用计数的智能指针 C++ 库中提供的不带引用计数的智能指针主要包括:auto_ptr,scoped_ptr,unique_ptr ,下面一一进行介绍。
1. 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 template <class _Ty > class auto_ptr { public : typedef _Ty element_type; explicit auto_ptr (_Ty * _Ptr = nullptr ) noexcept : _Myptr(_Ptr) { } auto_ptr (auto_ptr& _Right) noexcept : _Myptr(_Right.release ()) { } _Ty * release () noexcept { _Ty * _Tmp = _Myptr; _Myptr = nullptr ; return (_Tmp); }private : _Ty * _Myptr; };
从 auto_ptr
的源码可以看到,只有最后一个 auto_ptr
智能指针持有资源,原来的 auto_ptr
都被赋 nullptr
了,考虑如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;int main () { auto_ptr<int > p1 (new int ) ; auto_ptr<int > p2 (p1) ; *p1 = 10 ; cout << "p1 = " << *p1 << endl; return 0 ; }
上面的程序,如果用户不了解 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 #include <iostream> #include <vector> using namespace std;int main () { vector<auto_ptr<int >> vec; vec.push_back (auto_ptr <int >(new int (10 ))); vec.push_back (auto_ptr <int >(new int (20 ))); vec.push_back (auto_ptr <int >(new int (30 ))); cout << "vec[0] = " << *vec[0 ] << endl; vector<auto_ptr<int >> vec2 = vec; cout << "vec[0] = " << *vec[0 ] << endl; return 0 ; }
所以不要在容器中使用 auto_ptr
,C++ 建议最好不要使用 auto_ptr ,除非应用场景非常简单。
【总结】:auto_ptr 智能指针不带引用计数,那么它处理浅拷贝的问题,是直接把前面的 auto_ptr 都置为 nullptr,只让最后一个 auto_ptr 持有资源。
2. scoped_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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 template <class T > class scoped_ptr {private : T * px; scoped_ptr (scoped_ptr const &); scoped_ptr & operator =(scoped_ptr const &); typedef scoped_ptr<T> this_type; void operator ==( scoped_ptr const & ) const ; void operator !=( scoped_ptr const & ) const ; public : typedef T element_type; explicit scoped_ptr ( T * p = 0 ) : px( p ) // never throws { #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) boost::sp_scalar_constructor_hook ( px );#endif } #ifndef BOOST_NO_AUTO_PTR explicit scoped_ptr ( std::auto_ptr<T> p ) BOOST_NOEXCEPT : px( p.release() ) { #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) boost::sp_scalar_constructor_hook ( px );#endif } #endif ~scoped_ptr () {#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) boost::sp_scalar_destructor_hook ( px );#endif boost::checked_delete ( px ); } };
相当于:
1 2 scoped_ptr (scoped_ptr const &) = delete ; scoped_ptr & operator =(scoped_ptr const &) = delete ;
从 scoped_ptr
的源码中可以看到,该智能指针私有化了拷贝构造函数和operator=
重载赋值函数,因此从根本上杜绝了智能指针浅拷贝的发生,所以 scoped_ptr 也是不能用在容器当中的,如果容器互相进行拷贝或者赋值,就会引起 scoped_ptr 对象的拷贝构造和赋值,这是不允许的,代码会提示编译错误 。
auto_ptr
和 scoped_ptr
这一点上的区别,有些资料上用所有权 的概念来描述,道理是相同的,auto_ptr 可以任意转移资源的所有权,而 scoped_ptr 不会转移所有权 (因为拷贝构造和赋值被禁止了)。
3. unique_ptr
源码 要深入了解 unique_ptr
,需要先了解 C++ 的右值引用原理。
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 template <class _Ty , class _Dx > class unique_ptr : public _Unique_ptr_base<_Ty, _Dx> { public : typedef _Unique_ptr_base<_Ty, _Dx> _Mybase; typedef typename _Mybase::pointer pointer; typedef _Ty element_type; typedef _Dx deleter_type; unique_ptr (unique_ptr&& _Right) noexcept : _Mybase(_Right.release (), _STD forward<_Dx>(_Right.get_deleter ())) { } unique_ptr& operator =(unique_ptr&& _Right) noexcept { if (this != _STD addressof (_Right)) { reset (_Right.release ()); this ->get_deleter () = _STD forward<_Dx>(_Right.get_deleter ()); } return (*this ); } void swap (unique_ptr& _Right) noexcept { _Swap_adl(this ->_Myptr(), _Right._Myptr()); _Swap_adl(this ->get_deleter (), _Right.get_deleter ()); } ~unique_ptr () noexcept { if (get () != pointer ()) { this ->get_deleter ()(get ()); } } _NODISCARD pointer operator ->() const noexcept { return (this ->_Myptr()); } _NODISCARD pointer get () const noexcept { return (this ->_Myptr()); } explicit operator bool () const noexcept { return (get () != pointer ()); } pointer release () noexcept { pointer _Ans = get (); this ->_Myptr() = pointer (); return (_Ans); } void reset (pointer _Ptr = pointer()) noexcept { pointer _Old = get (); this ->_Myptr() = _Ptr; if (_Old != pointer ()) { this ->get_deleter ()(_Old); } } unique_ptr (const unique_ptr&) = delete ; unique_ptr& operator =(const unique_ptr&) = delete ; };
从源码中可以看出,unique_ptr
有一点和 scoped_ptr
做的一样,就是去掉了拷贝构造函数和 operator=
赋值重载函数,静止了用于对 unique_ptr 进行显示的拷贝和赋值,放置智能指针浅拷贝问题的发生。
但是 unique_ptr 提供了带右值引用参数的拷贝构造和赋值 ,也就是说,unique_ptr
智能指针可以通过右值引用进行拷贝构造和赋值操作,或者在产生 unique_ptr
临时对象的地方,如把 unique_ptr
作为函数的返回值时,示例代码如下:
示例1:
1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> using namespace std;int main () { unique_ptr<int > ptr1 (new int ) ; unique_ptr<int > ptr2 = std::move (ptr1); ptr2 = std::move (ptr1); return 0 ; }
示例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> using namespace std;unique_ptr<int > test_uniqueptr () { unique_ptr<int > ptr1 (new int ) ; return ptr1; }int main () { unique_ptr<int > ptr = test_uniqueptr (); return 0 ; }
unique_ptr
还提供了 reset 重置资源,swap 交换资源等函数,也经常会使用到。
可以看到,unique_ptr 从名字就可以看出来,最终也是只能有一个该智能指针引用资源,因此建议在使用不带引用计数的智能指针时,可以优先选择 unique_ptr 智能指针 。
3. 带引用计数的智能指针 带引用计数的智能指针可以实现多个智能指针管理同一个资源。 通过给每个被管理的资源匹配一个引用计数 来实现。当新增一个智能指针指向该资源时,引用计数 +1,当减少一个智能指向该资源是,引用计数 -1,知道引用计数为 0 时,资源被释放掉。由最后一个智能指针的析构函数来处理资源的释放问题,这就是引用计数的概念。
带引用计数:多个智能指针可以管理同一个资源
带引用计数:给每一个对象资源,匹配一个引用计数
智能指针 -> 引用资源的时候 -> 引用计数 +1
智能指针 -> 不适用资源的时候 -> 引用计数 -1 -> != 0 不释放资源,为 0 则释放资源
库里面的 shared_ptr
和 weak_ptr
引用计数的加减是线程安全的,因为用 atomic 定义了引用计数。
计数实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 template <class T >class RefCnt {public : RefCnt (T* ptr = nullptr ) : mptr (ptr) { if (mptr != nullptr ) { mcount = 1 ; } } void addRef () { mcount++; } int delRef () { return --mcount; }private : T* mptr; int mcount; };
智能指针类:
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 template <class T >class CSmartPtr {public : CSmartPtr (T* ptr = nullptr ) : mptr (ptr) { mpRefCnt = new RefCnt <T>(mptr); } ~CSmartPtr () { if (mpRefCnt->delRef () == 0 ) { delete mptr; mptr = nullptr ; } } T& operator *() { return *mptr; } T* operator ->() { return mptr; } CSmartPtr (CSmartPtr<T>& src) : mptr (src.mptr) , mpRefCnt (src.mpRefCnt) { if (mptr != nullptr ) { mpRefCnt->addRef (); } } CSmartPtr<T>& operator =(const CSmartPtr<T>& src) { if (this == &src) { return *this ; } if (mpRefCnt->delRef () == 0 ) { delete mptr; } mptr = src.mptr; mpRefCnt = src.mpRefCnt; mpRefCnt->addRef (); }private : T* mptr; RefCnt<T>* mpRefCnt; };
main 函数测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int main () { CSmartPtr<int > ptr1 (new int ) ; CSmartPtr<int > ptr2 (ptr1) ; CSmartPtr<int > ptr3; ptr3 = ptr2; *ptr1 = 20 ; cout << *ptr2 << " " << *ptr3 << endl; return 0 ; }
1. shared_ptr
实现 内部大概实现:每次复制,多一个共享同处资源的 shared_ptr
时,计数 +1。每次释放 shared_ptr
时,计数 -1。 当 shared 计数为 0 时,则证明所有指向同一处资源的 shared_ptr
们全都释放了,则随即释放该资源(还会释放 new 出来的 SharedPtrControlBlock)。
1 2 3 4 5 6 7 8 9 10 11 12 struct SharedPtrControlBlock { int shared_count; };template <class T >class shared_ptr { T* ptr; SharedPtrControlBlock* count; };
shared_ptr 是强智能指针,可以改变资源的引用计数
循环引用问题,造成 new 出来的资源无法释放,资源泄露
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 #include <iostream> using namespace std;class B ;class A {public : A () { cout << "A()" << endl; } ~A () { cout << "~A()" << endl; } shared_ptr<B> ptrb; };class B {public : B () { cout << "B()" << endl; } ~B () { cout << "~B()" << endl; } shared_ptr<A> ptra; };int main () { shared_ptr<A> pa (new A()) ; shared_ptr<B> pb (new B()) ; pa->ptrb = pb; pb->ptra = pa; cout << pa.use_count () << endl; cout << pb.use_count () << endl; }
首先,初始化时:
1 2 shared_ptr<A> pa (new A()) ;shared_ptr<B> pb (new B()) ;
shared_ptr
创建后,栈上的指针指向了对应的对象上,时期计数 +1。
其次,指向
1 2 pa->ptrb = pb; pb->ptra = pa;
指针互相指向,其计数器又 +1。
这就是交叉引用问题。!!!!
最后,析构,无法释放对象中互相引用的内存。
如下表所示是 shared_ptr
特有的操作:
如何解决循环引用问题?
定义对象的时候,用强智能指针;引用对象的地方,使用弱智能指针
weak_ptr
之所以可以打破循环引用,是因为:将一个 weak_ptr
绑定到一个 shared_ptr
不会改变 shared_ptr
的引用计数
2. weak_ptr
weak_ptr
是为了辅助 shared_ptr
的存在,它只提供了对管理对象的一个访问手段,同时也可以实时动态地知道指向的对象是否存活。
(只有某个对象的访问权,而没有它的生命控制权 即是 弱引用,所以 weak_ptr
是一种弱引用型指针)
内部大概实现:
计数区域(SharedPtrControlBlock)结构体引进新的 int 变量 weak_count
,来作为弱引用计数。
每个 weak_ptr
都占指针的两倍空间,一个装着原始指针,一个装着计数区域的指针(和 shared_ptr
一样的成员)。
weak_ptr
可以由一个 shared_ptr
或者另一个 weak_ptr
构造。
weak_ptr
的构造和析构不会引起 shared_count
的增加或减少,只会引起 weak_count
的增加或减少。
被管理资源的释放只取决于 shared 计数,当 shared 计数为0,才会释放被管理资源,也就是说 weak_ptr 不控制资源的生命周期 。
但是计数区域的释放却取决于 shared 计数和 weak 计数,当两者均为 0 时,才会释放计数区域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct SharedPtrControlBlock { int shared_count; int weak_count; };template <class T >class weak_ptr { T* ptr; SharedPtrControlBlock* count; };
弱智能指针 weak_ptr
区别于 shared_ptr
之处在于:
weak_ptr
不会改变资源的引用计数,只是一个观察者的角色,通过观察 shared_ptr
来判定资源是否存在
weak_ptr
持有的引用计数,不是资源的引用计数,而是同一个资源的观察者的计数
weak_ptr
没有提供常用的指针操作,无法直接访问资源,需要先通过 lock方法提升为 shared_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 33 34 35 36 37 38 39 #include <iostream> #include <memory> using namespace std;class B ; class A {public : A () { cout << "A()" << endl; } ~A () { cout << "~A()" << endl; } weak_ptr<B> _ptrb; };class B {public : B () { cout << "B()" << endl; } ~B () { cout << "~B()" << endl; } weak_ptr<A> _ptra; };int main () { shared_ptr<A> ptra (new A()) ; shared_ptr<B> ptrb (new B()) ; ptra->_ptrb = ptrb; ptrb->_ptra = ptra; cout << ptra.use_count () << endl; cout << ptrb.use_count () << endl; return 0 ; }
weak_ptr
是弱智能指针,不会改变资源的引用计数
weak_ptr
-> (观察)shared_ptr
->(管理) 资源(内存)
注意:
weak_ptr
只是一个观察者,它并不能直接操纵资源,没有重载 ->
和*
运算符,所以不能 ->
这样输出,要用 lock 返回 shared_ptr
类型才能用 ->
4. 多线程访问共享对象问题 强弱智能指针解决的另一问题:多线程访问共享对象的线程安全问题
有一个用 C++ 写的开源网络库,muduo 库,作者陈硕
该源码中对于智能指针的应用非常优秀,其中借助 shared_ptr
和 weak_ptr
解决了这样一个问题,多线程访问共享对象的线程安全问题。
解释如下:线程 A 和线程 B 访问一个共享的对象,如果线程 A 正在析构这个对象的时候,线程 B 又要调用该共享对象的成员方法,此时可能线程 A 已经把对象析构完了,线程 B 再去访问该对象,就会发生不可预期的错误。
代码:
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 #include <iostream> #include <thread> using namespace std;class Test {public : Test () :_ptr(new int (20 )) { cout << "Test()" << endl; } ~Test () { delete _ptr; _ptr = nullptr ; cout << "~Test()" << endl; } void show () { cout << *_ptr << endl; }private : int * volatile _ptr; };void threadProc (Test* p) { std::this_thread::sleep_for (std::chrono::seconds (2 )); p->show (); }int main () { Test* p = new Test (); std::thread t1 (threadProc, p) ; delete p; t1.join (); return 0 ; }
运行上面的代码,发现在 main 主线程已经 delete 析构 Test 对象以后,子线程 threadProc 再去访问 Test 对象的 show 方法,无法打印出 *_ptr
的值 20。可以用 shared_ptr
和 weak_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 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 #include <iostream> #include <thread> #include <memory> using namespace std;class Test {public : Test () :_ptr(new int (20 )) { cout << "Test()" << endl; } ~Test () { delete _ptr; _ptr = nullptr ; cout << "~Test()" << endl; } void show () { cout << *_ptr << endl; }private : int * volatile _ptr; };void threadProc (weak_ptr<Test> pw) { std::this_thread::sleep_for (std::chrono::seconds (2 )); shared_ptr<Test> ps = pw.lock (); if (ps != nullptr ) { ps->show (); } }int main () { shared_ptr<Test> p (new Test) ; std::thread t1 (threadProc, weak_ptr<Test>(p)) ; t1.join (); return 0 ; }
运行上面的代码,show 方法可以打印出 20,因为 main 线程调用了 t1.join()
方法等待子线程结束,此时 pw 通过 lock 提升为 ps 成功,见上面代码示例。
如果设置 t1 为分离线程,让 main 主线程结束,p 智能指针析构,进而把 Test 对象析构,此时 show 方法已经不会被调用,因为在 threadProc 方法中,pw 提升到 ps 时,lock 方法判定 Test 对象已经析构,提升失败!main 函数代码可以如下修改测试:
1 2 3 4 5 6 7 8 9 10 11 int main () { shared_ptr<Test> p (new Test) ; std::thread t1 (threadProc, weak_ptr<Test>(p)) ; t1.detach (); return 0 ; }
该 main 函数运行后,最终的 threadProc 中,show 方法不会被执行到。以上是在多线程中访问共享对象时,对shared_ptr和weak_ptr的一个典型应用 。
5. 自定义删除器 在用智能指针管理的资源是堆内存,当智能指针出作用域的时候,在其析构函数中会 delete 释放堆内存资源,但是除了堆内存资源,智能指针还可以管理其它资源,比如:打开的文件,此时对于文件指针的关闭,就不能用 delete 了,这时需要自定义智能指针释放资源的方式,先看看 unique_ptr
智能指针的析构函数代码,如下:
1 2 3 4 5 6 7 ~unique_ptr () noexcept { if (get () != pointer ()) { this ->get_deleter ()(get ()); } }
从 unique_ptr
的析构函数可以看到,如果要实现一个自定义的删除器,实际上就是定义一个函数对象而已,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> using namespace std;class FileDeleter {public : void operator () (FILE* pf) { fclose (pf); } };int main () { unique_ptr<FILE, FileDeleter> filePtr (fopen("data.txt" , "w" )) ; return 0 ; }
当然这种方式需要定义额外的函数对象类型,不推荐,可以用 C++11 提供的函数对象 function 和 lambda 表达式更好的处理自定义删除器,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 int main () { unique_ptr<FILE, function<void (FILE*)>> filePtr (fopen ("data.txt" , "w" ), [](FILE *pf)->void {fclose (pf);}); unique_ptr<int , function<void (int *)>> arrayPtr (new int [100 ], [](int *ptr)->void {delete []ptr; }); return 0 ; }
想进一步了解智能指针,可以查看智能指针的源码实现,或者看 muduo 网络库的源码。