ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

智能指针:解决内存泄漏的利器

智能指针:解决内存泄漏的利器 智能指针出现的场景#includeiostream using namespace std; double Divide(int a, int b) { if (b 0) { throwDivide by zero condition !; } else { return (double)a / (double)b; } } void Func() { 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; }从以上代码可以看出尽管new了以后我们也delete了但是delete还是会因为抛异常没有执行导致内存泄漏所以我们需要在new以后捕获异常并释放内存再把异常抛出。连续的两个new和Divide都会抛异常处理起来会很麻烦所以就有了智能指针。RAIIResource Acquisiotion Initialization)RAII即资源获取即初始化是手动解决管理资源的标准方案在获取资源的时候把资源委托给一个对象利用对象的生命周期来管理获取到的动态资源他的核心思想为1.资源获取放在构造函数里2.资源释放放在析构函数里3.对象的生命周期由栈自动管理4.当对象离开作用域的时候析构函数自动调用无论是正常退出还是异常抛出智能指针智能能指针是RAII思想的典型实例之一是RAII这个通用设计范式在堆内存管理场景下的产物。即智能指针是一个持有指针的栈上对象他的构造函数接收new出来的裸指针接管内存资源。析构自动执行delete或者delete[]释放内存离开作用域自动执行析构。下面是智能指针的简易设计思路#includeiostream using namespace std; templateclass T class SmartPtr { public: 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) { if (b 0) { throwDivide by zero condition !; } else { return (double)a / (double)b; } } void Func() { 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 (...) { cout 未知异常 endl; } return 0; }同时该代码还未解决以下问题1.当两个对象的指针共同指向同一块资源的时候一块资源会释放两次程序会崩溃解决方案为禁止拷贝2.析构已经写为 delete[]如果传入 new int 单个对象析构调用delete[]是未定义行为。C标准库中的智能指针c标准库的智能指针都包含在memory头文件下auto_ptr:拷贝时会把被拷贝对象的资源管理权转移给另外一个指针被拷贝对象的资源会悬空unique_ptr:不支持拷贝只支持移动构造他也是只保持一个对象的指针来管理资源但是这个资源管理权的转移使用者是清楚的shared_ptr:支持移动也支持拷贝底层用引用计数实现shared_ptr和weak_ptr循环引用如下图代码所表示的情况#includeiostream using namespace std; #includememory struct ListNode { int _data; shared_ptrListNode _next; shared_ptrListNode _prev; ~ListNode() { cout ListNode endl; } }; int main() { shared_ptrListNode n1(new (ListNode)); 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; return 0; }当n1,n2释放后两个ListNode节点的计数都减为1把两个节点分别看为左右两个节点想要右边的节点释放就需要左边的_next析构而作为左边的成员左边的节点释放_next才能析构想要左边的节点释放右边管理他的指针_prev又需要先去释放就又回到了右边节点的释放weak_ptr这个时候将结构体中的_next和_prev改为weak_ptr即可因为他不增加引用计数只绑定到shared_ptr因为如果他绑定的shared_ptr指向的资源已经释放那么他的访问是不合法的想要通过它去访问资源就需要调用lock来返回一个管理资源的shared_ptr,如果资源已经释放就返回空对象注意expired和use_count的使用#includeiostream using namespace std; #includememory int main() { shared_ptrstring sp1(new string(111111)); shared_ptrstring sp2(sp1); weak_ptrstring wp sp2; cout wp.expired() endl; cout wp.use_count() endl endl; sp1 make_sharedstring(22222); cout wp.expired() endl; cout wp.use_count() endl endl; sp2 make_sharedstring(22222); cout wp.expired() endl; cout wp.use_count() endl endl; wp sp1; cout wp.expired() endl; cout wp.use_count() endl endl; shared_ptrstringsp3 wp.lock(); cout wp.expired() endl; cout wp.use_count() endl endl; *sp3 ; cout *sp3 endl; return 0; }线程安全问题shared_ptr的引用计数对象在堆上如果shared_ptr在多个线程中进行拷贝或者析构时会同时访问修改引用计数就会存在线程安全问题同时shared_ptr指向的对象也存在线程安全问题需要做一下更改同时需要互斥锁#includefunctional #includeiostream using namespace std; #includememory #includemutex template class T class myshared_ptr { public: explicit myshared_ptr(T* ptr) :_ptr(ptr) , _pcount(new atomicint(1)) { } template class D myshared_ptr(T* ptr, D del) : _ptr(ptr) , _pcount(new atomicint(1)) , _del(del) { } myshared_ptr(const myshared_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; } } myshared_ptrT operator(const myshared_ptrT sp) { if (_ptr ! sp._ptr) { release(); _ptr sp._ptr; _pcount sp._pcount; (*_pcount); _del sp._del; } return *this; } ~myshared_ptr() { release(); } T* get() { return _ptr; } int use_count()const { return *_pcount; } T operator*() { return *_ptr; } T* operator-() { return _ptr; } private: T* _ptr; atomicint* _pcount; functionvoid(T*)_del [](T* ptr) {delete ptr; }; }; struct AA { int _a1 0; int _a2 0; ~AA() { cout ~AA endl; } }; int main() { myshared_ptrAA p(new AA); const size_t n 10000; mutex mtx; auto func []() { for (size_t i 0; i n; i) { myshared_ptrAA copy(p); { unique_lockmutexlk(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; }删除器1.智能指针在析构时默认delete释放资源所以如果不是new出来的资源交给智能指针管理析构时就会崩溃2.智能指针支持在构造是给一个可调用对象来实现想要的资源释放方式及删除器智能指针析构时调用删除器来释放资源需要特别主义的是unique_ptr删除器写在模板参数使用lambda以及函数指针的时候需要传删除器实例因为他们不支持默认构造shared_ptr删除器写在构造函数参数#includememory #includeiostream using namespace std; struct Date { int _year; int _month; int _day; Date(int year 1,int month 2,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)const { delete[] ptr; } }; class Fclose { public: void operator()(FILE* ptr) { cout fclose: ptr endl; fclose(ptr); } }; int main() { //仿函数删除器 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 del [](Date* ptr) {delete[] ptr; }; //lambda默认无类型需要decltype获取类型 unique_ptrDate, decltype(del) up4(new Date[5], del); shared_ptrDate sp4(new Date[5], del); 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; }3.new[]使用频繁unique_ptr和shared_ptr都提供了特化版本#includememory #includeiostream using namespace std; struct Date { int _year; int _month; int _day; Date(int year 1,int month 2,int day 1) :_year(year) ,_month(month) ,_day(day) { } ~Date() { cout ~Date() endl; } }; int main() { //特化版本 unique_ptrDate[] up1(new Date[5]); unique_ptrDate[] sp1(new Date[5]); }4.shared_ptr/unique_ptr重载了operator bool 可以智能指针是否持有资源当智能指针管理有效资源返回true5.make_shared:一个函数模板需要显示实例化可以把计数的内存和资源的内存开辟在一起因为碎片化的内存由内存碎片问题#includememory #includeiostream using namespace std; struct Date { int _year; int _month; int _day; Date(int year 1,int month 2,int day 1) :_year(year) ,_month(month) ,_day(day) { } ~Date() { cout ~Date() endl; } }; int main() { shared_ptrDate sp1(new Date(2025, 6, 1)); shared_ptrDatesp2 make_sharedDate(2025, 7, 17); auto sp3 make_sharedDate(2025, 7, 17); return 0; }6.explict:禁止构造函数隐式类型转换下面代码智能指针的构造函数没有被explicit修饰的情况当普通指针能够隐士类型转化为智能指针func()函数结束普通指针指向的内存被析构p变为野指针#includememory #includeiostream using namespace std; struct Date { int _year; int _month; int _day; ~Date() { cout ~Date() endl; } }; void func(shared_ptrDate sp) { cout func() endl; } int main() { Date* p new Date(); func(shared_ptrDate(p)); cout 函数结束 endl; cout p-_year endl; return 0; }智能指针的模拟实现auto_ptrtemplate class T class myauto_ptr { public: myauto_ptr(T* ptr) :_ptr(ptr) { } myauto_ptr(myauto_ptrT sp) :_ptr(sp._ptr) { sp._ptr nullptr; } myauto_ptrT operator(myauto_ptrT ap) { if (this ! ap) { if (_ptr) delete _ptr; _ptr ap._ptr; ap._ptr nullptr; } return *this; } ~myauto_ptr() { if (_ptr) { cout delete: _ptr endl; delete _ptr; } } T operator*() { return *_ptr; } T* operator-() { return _ptr; } private: T* _ptr; };unique_ptrtemplateclass T class myunique_ptr { myunique_ptr(T*ptr) :_ptr(ptr) { } ~myunique_ptr() { if (_ptr) { cout delete; _ptr endl; delete _ptr; } } T operator*() { return *_ptr; } T* operator-() { return _ptr; } myunique_ptr(const myunique_ptrT sp) delete; myunique_ptrT operator(const myunique_ptrT sp) delete; myunique_ptr(myunique_ptrT sp) :_ptr(sp._ptr) { sp._ptr nullptr; } myunique_ptrT operator(myunique_ptrT sp) { delete _ptr; _ptr sp._ptr; sp._ptr nullptr; } private: T* _ptr; };shared_ptr#includefunctional template class T class myshared_ptr { public: explicit myshared_ptr(T* ptr) :_ptr(ptr) , _pcount(new int(1)) { } template class D myshared_ptr(T* ptr, D del) : _ptr(ptr) , _pcount(new int(1)) , _del(del) { } myshared_ptr(const myshared_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; } } myshared_ptrT operator(const myshared_ptrT sp) { if (_ptr ! sp._ptr) { release(); _ptr sp._ptr; _pcount sp._pcount; (*_pcount); _del sp._del; } return *this; } ~myshared_ptr() { release(); } T* get() { 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; }; };weak_ptrtemplateclass T class myweak_ptr { public: myweak_ptr(const myshared_ptrT sp) :_ptr(sp.get()) { } myweak_ptrT operator (const myshared_ptrT sp) { _ptr sp.get(); return *this; } private: T*_ptr nullptr; };
返回列表