智能指针目录智能指针一、智能指针的使用场景二、RAII和智能指针三、智能指针的问题四、C标准库的智能指针4.1.auto_ptr4.2.唯一指针unique_ptr4.3.共享指针shared_ptr4.4.弱指针weak_ptr4.5.删除器4.6.make_share五、智能指针的原理5.1.auto_ptr原理5.2.unique_ptr原理5.3.share_ptr原理5.4.weak_ptr原理六、share_ptr循环引用问题七、share_ptr的线程安全问题八、内存泄漏8.1.内存泄漏的概念8.2.内存泄漏的危害8.3.内存泄漏的避免一、智能指针的使用场景new完对象后也使用了delete但是因为抛异常后面的delete没有执行导致内存泄漏需要new完对象后捕获异常捕获到异常后delete内存再把异常抛出但因为new本身也可能抛异常#define _CRT_SECURE_NO_WARNINGS 1 #include iostream using namespace std; #include string #include exception double Divide(int a, int b) { // 当b 0时抛出异常 if (b 0) { throw Divide by zero condition!; } else { return (double)a / (double)b; } } void Func() { //如果发生除0错误抛出异常另外下面的array和array2没有得到释放 //这里捕获异常后并不处理异常异常还是交给外面处理这里捕获了再重新抛出去 //但是如果array2new的时候抛异常呢就还需要套一层捕获释放逻辑这里更好解决方案是智能指针 int* array1 new int[10]; int* array2 new int[10]; try { int len, time; cin len time; cout Divide(len, time) endl; } catch (...) { cout delete [] array1 endl; cout delete [] array2 endl; delete[] array1; delete[] array2; throw; //异常重新抛出捕获到什么抛出什么 } //... cout delete [] array1 endl; delete[] array1; cout delete [] array2 endl; delete[] array2; } int main() { try { Func(); } catch (const char* errmsg) { cout errmsg endl; } catch (const exception e) { cout e.what() endl; } catch (...) { cout 未知异常 endl; } return 0; }二、RAII和智能指针RAIIResource Acquisition Is Initialization资源获取立即初始化一种管理资源的类的设计思想本质利用对象的生命周期来管理获取到的动态资源避免资源泄露注这里的资源指的是内存、文件指针、网络链接、互斥锁...RAII在获取资源是把资源委托给一个对象控制对资源的访问资源在对象的生命周期内始终保持有效在对象析构时被释放既保障了资源的正常释放、又可以避免资源泄露问题智能指针一个类模板用来实现RAII的设计思路方便资源访问注智能指针类像迭代器一样重载operator*/operator-/operator[]等运算符#define _CRT_SECURE_NO_WARNINGS 1 #include iostream using namespace std; #include string #include exception templateclass T class SmartPtr { public: //RAII SmartPtr(T* ptr) :_ptr(ptr) { } ~SmartPtr() { cout delete[] _ptr endl; delete[] _ptr; } //重载运算符模拟指针的行为方便访问资源 T operator*() { return *_ptr; } T* operator-() { return _ptr; } T operator[](size_t i) { return _ptr[i]; } private: T* _ptr; }; double Divide(int a, int b) { // 当b 0时抛出异常 if (b 0) { throw Divide by zero condition!; } else { return (double)a / (double)b; } } void Func() { //这里使用RAII的智能指针类管理new出来的数组以后程序简单多了 SmartPtrint sp1 new int[10];//隐式类型转换 SmartPtrint sp2 new int[10]; for (size_t i 0; i 10; i) { sp1[i] sp2[i] i; } int len, time; cin len time; cout Divide(len, time) endl; } int main() { try { Func(); } catch (const char* errmsg) { cout errmsg endl; } catch (const exception e) { cout e.what() endl; } catch (...) { cout 未知异常 endl; } return 0; }三、智能指针的问题int main() { //需要sp1和sp2共同管理资源浅拷贝 //但是析构多次的问题需要被解决 SmartPtrint sp1 new int[10]; SmartPtrint sp2(sp1); return 0; }四、C标准库的智能指针C标准库中的智能指针都在memory头文件智能指针有好多种类型除了weak_ptr他们都符合RAII原理上而言主要是解决智能指针拷贝时的思路不同4.1.auto_ptrC98时设计出来的智能指针拷贝时把被拷贝对象资源的管理权转移给拷贝对象导致被拷贝的对象悬空发生访问报错的问题不建议使用struct Date { int _year; int _month; int _day; Date(int year 1, int month 1, int day 1) :_year(year) ,_month(month) ,_day(day) {} ~Date() { cout ~Date() endl; } }; int main() { auto_ptrDate ap1(new Date); //拷贝时管理权限转移被拷贝对象ap1悬空 auto_ptrDate ap2(ap1); //空指针访问ap1对象已经悬空 //ap1-_year; return 0; }4.2.唯一指针unique_ptrC11时设计出来的智能指针不支持拷贝只支持移动建议在不需要拷贝的场景使用int main() { unique_ptrDate up1(new Date); //不支持拷⻉ //unique_ptrDate up2(up1); //支持移动但是移动后up1也悬空所以使用移动要谨慎 unique_ptrDate up3(move(up1)); return 0; }4.3.共享指针shared_ptrC11时设计出来的智能指针支持拷贝也支持移动可以在需要拷贝的场景时使用底层用引用计数实现int main() { shared_ptrDate sp1(new Date); //支持拷贝 shared_ptrDate sp2(sp1); shared_ptrDate sp3(sp2); cout sp1.use_count() endl; sp1-_year; cout sp1-_year endl; cout sp2-_year endl; cout sp3-_year endl; //支持移动 //但是移动后sp1也悬空所以使用移动要谨慎 shared_ptrDate sp4(move(sp1)); return 0; }4.4.弱指针weak_ptrC11时设计出来的智能指针不支持RAII无法用它直接管理资源解决share_ptr的循环引用缺陷4.5.删除器智能指针在析构时默认进行delete释放资源如果不是new出来的资源交给智能指针管理析构时会崩溃智能指针在构造时给一个删除器删除器一个可调用的对象在对象中实现你想要的释放资源方式#define _CRT_SECURE_NO_WARNINGS 1 #include iostream using namespace std; struct Date { int _year; int _month; int _day; Date(int year 1, int month 1, int day 1) :_year(year) ,_month(month) ,_day(day) {} ~Date() { cout ~Date() endl; } }; template class T void DeleteArrayFunc(T * ptr) { delete[] ptr; } template class T class DeleteArray { public: void operator()(T * ptr) { delete[] ptr; } }; class Fclose { public: void operator()(FILE * ptr) { cout fclose: ptr endl; fclose(ptr); } }; int main() { //这样实现程序会崩溃 unique_ptrDate up1(new Date[10]); shared_ptrDate sp1(new Date[10]); //解决方案1特化 //因为new[]经常使用所以unique_ptr和shared_ptr //实现了一个特化版本这个特化版本析构时用的delete[] unique_ptrDate[] up1(new Date[5]); shared_ptrDate[] sp1(new Date[5]); //解决方案2删除器 //仿函数对象做删除器 //unique_ptrDate, DeleteArrayDate up2(new Date[5], DeleteArrayDate()); //unique_ptr和shared_ptr支持删除器的方有所不同 //unique_ptr是在类模板参数支持的 //shared_ptr是构造函数参数支持的 //使用仿函数unique_ptr可以不在构造函数传递 //因为仿函数类型构造的对象直接就可以调用 //但是下面的函数指针和lambda的类型不可以 unique_ptrDate, DeleteArrayDate up2(new Date[5]); shared_ptrDate sp2(new Date[5], DeleteArrayDate()); //函数指针做删除器 unique_ptrDate, void(*)(Date*) up3(new Date[5], DeleteArrayFuncDate); shared_ptrDate sp3(new Date[5], DeleteArrayFuncDate); //lambda表达式做删除器 auto delArrOBJ [](Date* ptr) {delete[] ptr;}; unique_ptrDate, decltype(delArrOBJ) up4(new Date[5], delArrOBJ); shared_ptrDate sp4(new Date[5], delArrOBJ); //实现其他资源管理的删除器 shared_ptrFILE sp5(fopen(Test.cpp, r), Fclose()); shared_ptrFILE sp6(fopen(Test.cpp, r), [](FILE* ptr) { cout fclose: ptr endl; fclose(ptr);}); return 0; }4.6.make_shareint main() { shared_ptrDate sp1(new Date(2024, 9, 11)); shared_ptrDate sp2 make_sharedDate(2024, 9, 11); auto sp3 make_sharedDate(2024, 9, 11); shared_ptrDate sp4; // if (sp1.operator bool()) if (sp1) cout sp1 is not nullptr endl; if (!sp4) cout sp1 is nullptr endl; // 报错 shared_ptrDate sp5 new Date(2024, 9, 11); unique_ptrDate sp6 new Date(2024, 9, 11); return 0; }五、智能指针的原理5.1.auto_ptr原理拷贝时转移资源管理权给被拷贝的对象templateclass T class auto_ptr { public: auto_ptr(T* ptr) :_ptr(ptr) {} auto_ptr(auto_ptrT sp) :_ptr(sp._ptr) { //管理权转移 sp._ptr nullptr; } auto_ptrT operator(auto_ptrT 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; };5.2.unique_ptr原理不支持拷贝只支持移动template class T class unique_ptr { public: explicit 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_ptrTsp) delete; unique_ptrToperator(const unique_ptrTsp) delete; unique_ptr(unique_ptrT sp) :_ptr(sp._ptr) { sp._ptr nullptr; } unique_ptrToperator(unique_ptrT sp) { delete _ptr; _ptr sp._ptr; sp._ptr nullptr; } private: T * _ptr; };5.3.share_ptr原理设计引用计数一份资源就需要一个引用计数所以引用计数用静态成员的方式是无法实现的要使用堆上动态开辟的方式构造智能指针对象时来一份资源就要new一个引用计数增加share_ptr指向资源就引用计数当shared_ptr对象析构时就--引用计数引用计数减到0时代表当前析构的share_ptr是最后一个管理资源的对象则析构资源#define _CRT_SECURE_NO_WARNINGS 1 template class T class shared_ptr { public: explicit shared_ptr(T * ptr nullptr) : _ptr(ptr) ,_pcount(new int(1)) {} templateclass D shared_ptr(T * ptr, D del) :_ptr(ptr) ,_pcount(new int(1)) ,_del(del) {} shared_ptr(const shared_ptrTsp) :_ptr(sp._ptr) ,_pcount(sp._pcount) ,_del(sp._del) { (*_pcount); } void release() { if (--(*_pcount) 0) { // 最后一个管理的对象释放资源 _del(_ptr); delete _pcount; _ptr nullptr; _pcount nullptr; } } shared_ptrT operator(const shared_ptrT sp) { if (_ptr ! sp._ptr) { release(); _ptr sp._ptr; _pcount sp._pcount; (*_pcount); _del sp._del; } return *this; } ~shared_ptr() { release(); } T * get() const { return _ptr; } int use_count() const { return *_pcount; } T operator*() { return *_ptr; } T * operator-() { return _ptr; } private: T * _ptr; int* _pcount; functionvoid(T*) _del [](T* ptr) {delete ptr;}; };5.4.weak_ptr原理weak_ptr不支持RAII也不支持访问资源绑定到share_ptr时不增加share_ptr的引用计数可以解决循环引用问题template class T class weak_ptr { public: weak_ptr() {} weak_ptr(const shared_ptrTsp) : _ptr(sp.get()) {} weak_ptrToperator(const shared_ptrTsp) { _ptr sp.get(); return *this; } private: T* _ptr nullptr; }; int main() { std::shared_ptrstring sp1(new string(111111)); std::shared_ptrstring sp2(sp1); std::weak_ptrstring wp sp1; cout wp.expired() endl; cout wp.use_count() endl; // sp1和sp2都指向了其他资源则weak_ptr就过期了 sp1 make_sharedstring(222222); cout wp.expired() endl; cout wp.use_count() endl; sp2 make_sharedstring(333333); cout wp.expired() endl; cout wp.use_count() endl; wp sp1; //std::shared_ptrstring sp3 wp.lock(); auto sp3 wp.lock(); cout wp.expired() endl; cout wp.use_count() endl; *sp3 ###; cout *sp1 endl; return 0; }六、share_ptr循环引用问题struct ListNode { int _data; std::shared_ptrListNode _next; std::shared_ptrListNode _prev; ~ListNode() { cout ~ListNode() endl; } }; int main() { // 循环引⽤ -- 内存泄露 std::shared_ptrListNode n1(new ListNode); std::shared_ptrListNode n2(new ListNode); cout n1.use_count() endl; cout n2.use_count() endl; n1-_next n2; n2-_prev n1; return 0; }右边的节点什么时候释放左边节点中的_next管理右边节点_next析构后右边的节点释放_next什么时候析构_next是左边节点的成员左边节点释放后_next析构左边的节点什么时候释放右边节点中的_prev管理左边节点_prev析构后左边的节点释放_prev什么时候析构_prev是右边节点的成员右边节点释放后_prev析构以上逻辑形成了循环引用导致内存泄漏问题weak_ptr可以解决这个问题struct ListNode { int _data; //这里改成weak_ptr当n1-_next n2绑定shared_ptr时 //不增加n2的引⽤计数不参与资源释放的管理就不会形成循环引用 std::weak_ptrListNode _next; std::weak_ptrListNode _prev; ~ListNode() { cout ~ListNode() endl; } }; int main() { std::shared_ptrListNode n1(new ListNode); std::shared_ptrListNode n2(new ListNode); n1-_next n2; n2-_prev n1; cout n1.use_count() endl; cout n2.use_count() endl; //weak_ptr不支持管理资源不支持RAII //weak_ptr是专⻔绑定shared_ptr不增加引用计数作为⼀些场景的辅助管理 std::weak_ptrListNode wp(new ListNode); return 0; }七、share_ptr的线程安全问题share_ptr的引用计数对象在堆上如果多个shared_ptr对象在多个线程中进行share_ptr的拷贝析构时会访问修改引用计数就会存在线程安全问题所以shared_ptr引用计数是需要加锁或者原子操作保证线程安全share_ptr指向的对象也是有线程安全问题的但这个对象的线程安全不归share_ptr管也管不了下面的程序会崩溃或者A资源没释放bit::share_ptr引用计数从int*改成atomicint*就可以保证引用计数的线程安全问题struct AA { int _a1 0; int _a2 0; ~AA() { cout ~AA() endl; } }; int main() { bit::shared_ptrAA p(new AA); const size_t n 100000; mutex mtx; auto func []() { for (size_t i 0; i n; i) { //这里智能指针拷贝会计数 bit::shared_ptrAA copy(p); { unique_lockmutex lk(mtx); copy-_a1; copy-_a2; } } }; thread t1(func); thread t2(func); t1.join(); t2.join(); cout p-_a1 endl; cout p-_a2 endl; cout p.use_count() endl; return 0; }八、内存泄漏8.1.内存泄漏的概念因为疏忽或错误造成程序未能释放已经不再使用的内存一般是忘记释放或者发生异常释放程序未能执行导致并不是指内存在物理上的消失而是应用程序分配某段内存后因为设计错误失去对该段内存的控制造成内存浪费8.2.内存泄漏的危害普通程序运行一会就结束出现内存泄漏问题不大进程正常结束页表的映射关系解除物理内存也可以释放长期运行的程序出现内存泄漏影响很大如操作系统、后台服务、长期运行的客户端等不断出现内存泄漏会导致可用内存不断变少各种功能响应越来越慢最终卡死int main() { //申请⼀个1G未释放这个程序多次运行也没啥危害 //因为程序马上就结束进程结束各种资源也就回收了 char* ptr new char[1024 * 1024 * 1024]; cout (void*)ptr endl; return 0; }8.3.内存泄漏的避免申请的内存空间记得匹配的去释放用智能指针管理资源采用RAII思想自己造轮子处理定期使用内存泄漏工具检测