智能指针是 C++
中用于自动管理动态内存的类模板,它通过 RAII
(资源获取即初始化)技术避免手动 new
/ 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
| int div() { int a, b; cin >> a >> b; if (b == 0) throw invalid_argument("除0错误"); return a / b; }
void Func() { int* p1 = new int; int* p2 = new int; cout << div() << endl; delete p1; delete p2; }
int main() { try { Func(); } catch (exception& e) { cout << e.what() << endl; } return 0; }
|
如果 p1
这里 new
抛异常会如何?
p1
未成功分配,值为 nullptr
函数直接跳转到 catch
块,p2
未分配,无内存泄漏
如果 p2
这里 new
抛异常会如何?
p1
已分配但未释放,导致内存泄漏
函数跳转到 catch
块,p2
未分配,delete p1
和 delete p2
均未执行
如果 div
调用这里又会抛异常会如何?
p1
和 p2
均已分配但未释放,导致双重内存泄漏
函数跳转到 catch
块,打印错误信息(如 “除 0
错误”)
C++
不像 java
具有垃圾回收机制,能够自动回收开辟的空间,需要自行手动管理,但是自己管理有时又太麻烦了,况且这里只是两个指针就产生了这么多问题,因此在 C++11
就推出了智能指针用于自动管理内存
智能指针原理
RAll
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| template<class T> class SmartPtr { public: SmartPtr(T* ptr = nullptr) : _ptr(ptr) {} ~SmartPtr() { if (_ptr) delete _ptr; }
private: T* _ptr; };
int main() { SmartPtr<int> sp1(new int(1)); SmartPtr<string> sp2(new string("xxx")); return 0; }
|
RAII
(Resource Acquisition Is Initialization
)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术
简单来说,就是把创建的对象给到 SmartPtr
类来管理,当对象的生命周期结束的时候,刚好类也会自动调用析构函数进行内存释放
这种做法有两大好处:
- 不需要显式地释放资源
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
像指针一样使用
都叫做智能指针了,那肯定是可以当作指针一样使用了,指针可以解引用,也可
以通过 ->
去访问所指空间中的内容,因此类中还得需要将 *
、->
重载下,才可让其像指针一样去使用
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
| template<class T> class SmartPtr { public: SmartPtr(T* ptr) :_ptr(ptr) {}
~SmartPtr() { cout << "delete:" << _ptr << endl; delete _ptr; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; } private: T* _ptr; };
|
*
重载返回对象,->
重载返回地址,这部分的知识点在迭代器底层分析已经讲过很多遍了,就不过多叙述了,可自行翻阅前文
C++11的智能指针
智能指针一般放在 <memery>
文件里,C++11
也参考了第三方库 boost
C++ 98
中产生了第一个智能指针 auto_ptr
C++ boost
给出了更实用的 scoped_ptr
和 shared_ptr
和 weak_ptr
C++ TR1
,引入了 shared_ptr
等。不过注意的是 TR1
并不是标准版
C++ 11
,引入了 unique_ptr
和 shared_ptr
和 weak_ptr
。需要注意的是 unique_ptr
对应 boost
的 scoped_ptr
。并且这些智能指针的实现原理是参考 boost
中的实现的
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| template<class T> class auto_ptr { public: auto_ptr(T* ptr) :_ptr(ptr) {}
~auto_ptr() { cout << "delete:" << _ptr << endl; delete _ptr; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; } auto_ptr(auto_ptr<T>& ap) :_ptr(ap._ptr) { ap._ptr = nullptr; }
auto_ptr<T>& operator=(auto_ptr<T>& ap) { if (this != &ap) { _ptr = ap._ptr; ap._ptr = nullptr; } return *this; } private: T* _ptr; };
|
auto_ptr
在 C++98
就已经被引入,实现了智能指针如上面所讲的最基础的功能,同时他还额外对拷贝构造、=
重载进行了显式调用,但是这种拷贝虽然能解决新对象的初始化,但是对于被拷贝的对象,造成了指针资源所有权被转移走,跟移动构造有些类似
因此,auto_ptr
会导致管理权转移,拷贝对象被悬空,auto_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 23 24 25 26 27 28 29 30 31 32 33 34
| template<class T> class unique_ptr { public: unique_ptr(T* ptr) :_ptr(ptr) {}
~unique_ptr() { cout << "delete:" << _ptr << endl; delete _ptr; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
unique_ptr(unique_ptr<T>& ap) = delete; unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete; private: T* _ptr; };
|
unique_ptr
很简单粗暴,直接禁止了拷贝机制
因此,建议在不需要拷贝的场景使用该智能指针
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 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
| template<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) :_ptr(ptr) ,_pcount(new int(1)) {}
~shared_ptr() { if (--(*_pcount) == 0) { cout << "delete:" << _ptr << endl; delete _ptr; delete _pcount; } }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr) ,_pcount(sp._pcount) { ++(*_pcount); }
shared_ptr<T>& operator=(const shared_ptr<T>& sp) { if (_ptr == sp._ptr) return *this;
if (--(*_pcount) == 0) { delete _ptr; delete _pcount; }
_ptr = sp._ptr; _pcount = sp._pcount; ++(*_pcount);
return *this; }
int use_count() const { return *_pcount; }
T* get() const { return _ptr; }
private: T* _ptr; int* _pcount; };
|
C++11
中的智能指针就属 shared_ptr
使用的最多,因为它解决了赋值造成的资源被转移可能会被错误访问的问题
类中增加一个新的指针 _pcount
用于计数,即计数有多少个 _ptr
指向同一片空间,多个 shared_ptr
可以同时指向同一个对象,每次创建新的 shared_ptr
指向该对象,引用计数加 1
;每次 shared_ptr
析构或者被赋值为指向其他对象,引用计数减 1
。当最后一个指向该对象的 shared_ptr
析构时,对象会被自动删除,从而避免内存泄漏
🔥值得注意的是: shared_ptr
同时也支持了无法自己给自己赋值,这里还涉及一些关于线程安全的知识点,待 Linux
学习过后再来补充
weak_ptr
看似完美的 shared_ptr
其实也会有疏漏,比如:引用循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| struct ListNode { int _data; shared_ptr<ListNode> _next; shared_ptr<ListNode> _prev; }; 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; }
|
当执行 node1->next = node2
和 node2->prev = node1
时,node1
内部的 _next
指针指向 node2
,node2
内部的 _prev
指针指向 node1
。这就导致两个节点之间形成了循环引用关系。此时,由于互相引用,每个节点的引用计数都变为 2
,因为除了外部的智能指针引用,还多了来自另一个节点内部指针的引用
当 node1
和 node2
智能指针对象离开作用域开始析构时,它们首先会将所指向节点的引用计数减 1
。此时,每个节点的引用计数变为 1
,而不是预期的 0
。这是因为 node1
的 _next
还指向 node2
,node2
的 _prev
还指向 node1
,使得它们的引用计数无法归零
对于 shared_ptr
来说,只有当引用计数变为 0
时才会释放所管理的资源。由于这种循环引用的存在,node1
等待 node2
先释放(因为 node2
的 _prev
引用着 node1
),而 node2
又等待 node1
先释放(因为 node1
的 _next
引用着 node2
),最终导致这两个节点所占用的资源都无法被释放,造成内存泄漏
1 2 3 4 5 6
| class ListNode { public: weak_ptr<ListNode> _next; weak_ptr<ListNode> _prev; };
|
为了解决 shared_ptr
的循环引用问题,通常可以使用 weak_ptr
。weak_ptr
是一种弱引用智能指针,它不会增加所指向对象的引用计数。将循环引用中的某一个引用(比如 ListNode
类中的 _prev
或 _next
其中之一)改为 weak_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 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
| #include <iostream> #include <memory> #include <string>
using namespace std;
class A { public: ~A() { cout << "A::~A()" << endl; } };
template<class T> struct FreeFunc { void operator()(T* ptr) const { cout << "FreeFunc: free memory at " << ptr << endl; free(ptr); } };
template<class T> struct DeleteArrayFunc { void operator()(T* ptr) const { cout << "DeleteArrayFunc: delete[] memory at " << ptr << endl; delete[] ptr; } };
int main() { shared_ptr<int> sp1((int*)malloc(sizeof(int)), FreeFunc<int>()); *sp1 = 100; cout << "sp1: " << *sp1 << " at " << sp1.get() << endl;
shared_ptr<int> sp2(new int[5], DeleteArrayFunc<int>()); for (int i = 0; i < 5; ++i) { sp2.get()[i] = i; } cout << "sp2 array:"; for (int i = 0; i < 5; ++i) { cout << " " << sp2.get()[i]; } cout << endl;
shared_ptr<A> sp4(new A[3], [](A* p) { cout << "Lambda: deleting array at " << p << endl; delete[] p; }); cout << "sp4 array of A objects created" << endl;
shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p) { if (p) { cout << "Lambda: closing file" << endl; fclose(p); } }); if (sp5) { fprintf(sp5.get(), "Hello, shared_ptr with deleter!\n"); cout << "File written" << endl; }
return 0; }
|
对于所有的指针不一定是 new
出来的对象,因此利用仿函数设置了删除器,这样就可以调用对应的删除
希望读者们多多三连支持
小编会继续更新
你们的鼓励就是我前进的动力!
