智能指针,咕咕咕!

智能指针,咕咕咕! 1.智能指针的使用场景分析下⾯程序中我们可以看到new了以后我们也delete了但是因为抛异常导后⾯的delete没有得到执⾏所以就内存泄漏了所以我们需要new以后捕获异常捕获到异常后delete内存再把异常抛出但是因为new本⾝也可能抛异常连续的两个new和下⾯的Divide都可能会抛异常让我们处理起来很⿇烦。智能指针放到这样的场景⾥⾯就让问题简单多了。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; }2.RAII与智能指针的设计思路RAII是Resource Acquisition Is Initialization的缩写他是⼀种管理资源的类的设计思想本质是⼀种利⽤对象⽣命周期来管理获取到的动态资源避免资源泄漏这⾥的资源可以是内存、⽂件指针、⽹络连接、互斥锁等等。RAII在获取资源时把资源委托给⼀个对象接着控制对资源的访问资源在对象的⽣命周期内始终保持有效最后在对象析构的时候释放资源这样保障了资源的正常释放避免资源泄漏问题。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; }3.c标准智能指针使用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; unique_ptrDate up1(new Date); // 不⽀持拷⻉ //unique_ptrDate up2(up1); // ⽀持移动但是移动后up1也悬空所以使⽤移动要谨慎 unique_ptrDate up3(move(up1)); 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; }templateclass T void DeleteArrayFunc(T* ptr) { delete[] ptr; } templateclass 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; }int 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; }4.智能指针的原理下⾯我们模拟实现了auto_ptr和unique_ptr的核⼼功能这两个智能指针的实现⽐较简单⼤家了解⼀下原理即可。auto_ptr的思路是拷⻉时转移资源管理权给被拷⻉对象这种思路是不被认可的也不建议使⽤。unique_ptr的思路是不⽀持拷⻉。⼤家重点要看看shared_ptr是如何设计的尤其是引⽤计数的设计主要这⾥⼀份资源就需要⼀个引⽤计数所以引⽤计数才⽤静态成员的⽅式是⽆法实现的要使⽤堆上动态开辟的⽅式构造智能指针对象时来⼀份资源就要new⼀个引⽤计数出来。多个shared_ptr指向资源时就引⽤计数shared_ptr对象析构时就--引⽤计数引⽤计数减到0时代表当前析构的shared_ptr是最后⼀个管理资源的对象则析构资源。namespace bit { 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; }; templateclass 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_ptrT sp) delete; unique_ptrT operator(const unique_ptrT sp) delete; unique_ptr(unique_ptrT sp) :_ptr(sp._ptr) { sp._ptr nullptr; } unique_ptrT operator(unique_ptrT sp) { delete _ptr; _ptr sp._ptr; sp._ptr nullptr; } private: T* _ptr; }; templateclass 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_ptrT sp) :_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; //atomicint* _pcount; functionvoid(T*) _del [](T* ptr) {delete ptr; }; }; // 需要注意的是我们这⾥实现的shared_ptr和weak_ptr都是以最简洁的⽅式实现的 // 只能满⾜基本的功能这⾥的weak_ptr lock等功能是⽆法实现的想要实现就要 // 把shared_ptr和weak_ptr⼀起改了把引⽤计数拿出来放到⼀个单独类型shared_ptr // 和weak_ptr都要存储指向这个类的对象才能实现有兴趣可以去翻翻源代码 templateclass T class weak_ptr { public: weak_ptr() {} weak_ptr(const shared_ptrT sp) :_ptr(sp.get()) {} weak_ptrT operator(const shared_ptrT sp) { _ptr sp.get(); return *this; } private: T* _ptr nullptr; }; } int main() { bit::auto_ptrDate ap1(new Date); // 拷⻉时管理权限转移被拷⻉对象ap1悬空 bit::auto_ptrDate ap2(ap1); // 空指针访问ap1对象已经悬空 //ap1-_year; bit::unique_ptrDate up1(new Date); // 不⽀持拷⻉ //unique_ptrDate up2(up1); // ⽀持移动但是移动后up1也悬空所以使⽤移动要谨慎 bit::unique_ptrDate up3(move(up1)); bit::shared_ptrDate sp1(new Date); // ⽀持拷⻉ bit::shared_ptrDate sp2(sp1); bit::shared_ptrDate sp3(sp2); cout sp1.use_count() endl; sp1-_year; cout sp1-_year endl; cout sp2-_year endl; cout sp3-_year endl; return 0; }5.weak_ptr与shared_ptr(1)shared_ptr的循环引用问题struct ListNode { int _data; std::shared_ptrListNode _next; std::shared_ptrListNode _prev; // 这⾥改成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); cout n1.use_count() endl; cout n2.use_count() endl; 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; }(2)weak_ptrweak_ptr不⽀持RAII也不⽀持访问资源所以我们看⽂档发现weak_ptr构造时不⽀持绑定到资源只⽀持绑定到shared_ptr绑定到shared_ptr时不增加shared_ptr的引⽤计数那么就可以解决上述的循环引⽤问题。weak_ptr也没有重载operator*和operator-等因为他不参与资源管理那么如果他绑定的shared_ptr已经释放了资源那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的资源是否过期use_count也可获取shared_ptr的引⽤计数weak_ptr想访问资源时可以调⽤lock返回⼀个管理资源的shared_ptr如果资源已经被释放返回的shared_ptr是⼀个空对象如果资源没有释放则通过返回的shared_ptr访问资源是安全的。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; }6.shared_ptr的线程安全问题shared_ptr的引⽤计数对象在堆上如果多个shared_ptr对象在多个线程中进⾏shared_ptr的拷⻉析构时会访问修改引⽤计数就会存在线程安全问题所以shared_ptr引⽤计数是需要加锁或者原⼦操作保证线程安全的。shared_ptr指向的对象也是有线程安全的问题的但是这个对象的线程安全问题不归shared_ptr管它也管不了应该有外层使⽤shared_ptr的⼈进⾏线程安全的控制。下⾯的程序会崩溃或者A资源没释放bit::shared_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; }7.C11与boost智能指针的关系Boost库是为C语⾔标准库提供扩展的⼀些C程序库的总称Boost社区建⽴的初衷之⼀就是为C的标准化⼯作提供可供参考的实现Boost社区的发起⼈Dawes本⼈就是C标准委员会的成员之⼀。在Boost库的开发中Boost社区也在这个⽅向上取得了丰硕的成果C11及之后的新语法和库有很多都是从Boost中来的。C 98 中产⽣了第⼀个智能指针auto_ptr。C boost给出了更实⽤的scoped_ptr/scoped_array和shared_ptr/shared_array和weak_ptr等.C TR1引⼊了shared_ptr等不过注意的是TR1并不是标准版。C 11引⼊了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。8.内存泄漏什么是内存泄漏内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存⼀般是忘记释放或者发⽣异常释放程序未能执⾏导致的。内存泄漏并不是指内存在物理上的消失⽽是应⽤程序分配某段内存后因为设计错误失去了对该段内存的控制因⽽造成了内存的浪费。内存泄漏的危害普通程序运⾏⼀会就结束了出现内存泄漏问题也不⼤进程正常结束⻚表的映射关系解除物理内存也可以释放。⻓期运⾏的程序出现内存泄漏影响很⼤如操作系统、后台服务、⻓时间运⾏的客⼾端等等不断出现内存泄漏会导致可⽤内存不断变少各种功能响应越来越慢最终卡死。int main() { // 申请⼀个1G未释放这个程序多次运⾏也没啥危害 // 因为程序⻢上就结束进程结束各种资源也就回收了 char* ptr new char[1024 * 1024 * 1024]; cout (void*)ptr endl; return 0; }Linux内存泄漏检测的工具的文:文windows下用第三方工具:文⼯程前期良好的设计规范养成良好的编码规范申请的内存空间记着匹配的去释放。ps这个理想状态。但是如果碰上异常时就算注意释放了还是可能会出问题。需要下⼀条智能指针来管理才有保证。尽量使⽤智能指针来管理资源如果⾃⼰场景⽐较特殊采⽤RAII思想⾃⼰造个轮⼦管理。定期使⽤内存泄漏⼯具检测尤其是每次项⽬快上线前不过有些⼯具不够靠谱或者是收费。总结⼀下内存泄漏⾮常常⻅解决⽅案分为两种1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测⼯具。