智能网站排名优化,外国网页设计免费模板,WordPress ajax查询,个人做的好的淘宝客网站文章目录 1. 前言2. 为什么需要智能指针#xff1f;3. 内存泄漏3.1 内存泄漏的概念及危害3.2 内存泄漏的分类3.3 如何检测内存泄漏3.4 如何避免内存泄漏 4. 智能指针的使用及原理4.1 RAII思想4.2 智能指针的原理4.3 C智能指针发展历史4.4 std::auto_ptr4.5 std::unique_ptr4.6… 文章目录 1. 前言2. 为什么需要智能指针3. 内存泄漏3.1 内存泄漏的概念及危害3.2 内存泄漏的分类3.3 如何检测内存泄漏3.4 如何避免内存泄漏 4. 智能指针的使用及原理4.1 RAII思想4.2 智能指针的原理4.3 C智能指针发展历史4.4 std::auto_ptr4.5 std::unique_ptr4.6 std::shared_ptr4.7 shared_ptr的循环引用问题4.8 定制删除器 1. 前言 C提供了智能指针的概念它可以自动管理动态分配的内存资源。智能指针是一种对象它表现得像一个常规指针但具有附加的内存管理功能。本篇文章将着重讲解智能指针的作用、内存泄漏以及智能指针的使用和原理。 2. 为什么需要智能指针 在某些场景下我们在堆上申请了一块空间但是却无法正常释放。 下面我们先来分析下面这段程序有没有什么内存方面的问题 int div()
{int a, b;cin a b;if (b 0)throw invalid_argument(除0错误);return a / b;
}
void Func()
{// 1、如果p1这里new 抛异常会如何// 2、如果p2这里new 抛异常会如何// 3、如果div调用这里又会抛异常会如何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 分配内存时抛出异常那么异常将传播到 Func() 函数的调用点。由于没有捕获该异常的代码异常会继续传播到 main() 函数中的异常处理部分。此时p1 指针没有被成功分配内存因此不会发生内存泄漏。在异常处理部分您可以选择适当地清理和处理异常情况。如果在 p2 这里使用 new 分配内存时抛出异常与第一个问题类似异常也会传播到 Func() 函数的调用点。同样地没有捕获该异常的代码它会继续传播到 main() 函数中的异常处理部分。此时p1 指针已成功分配内存但 p2 指针没有成功分配内存。因此在异常处理部分中您需要确保释放已分配的内存delete p1并处理异常情况。如果在 div() 函数中抛出异常它会立即终止函数的执行并且异常会传播到调用 div() 的地方也就是 Func() 函数的调用点。同样地由于没有对该异常进行捕获它会继续传播到 main() 函数中的异常处理部分。在此过程中p1 和 p2 指针已成功分配内存因此您需要确保在异常处理部分中释放已分配的内存delete p1 和 delete p2并处理异常情况。 在异常处理部分我们可以选择清理资源、记录日志或向用户显示错误信息等。同时确保释放已分配的内存以避免内存泄漏。在使用 new 分配内存时可以使用智能指针如 std::unique_ptr 或 std::shared_ptr来管理动态内存而不是手动使用 new 和 delete。智能指针可以自动处理内存释放避免了手动释放内存的繁琐和容易出错的过程。 3. 内存泄漏
3.1 内存泄漏的概念及危害 什么是内存泄漏内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失而是应用程序分配某段内存后因为设计错误失去了对该段内存的控制因而造成了内存的浪费。 内存泄漏的危害长期运行的程序出现内存泄漏影响很大如操作系统、后台服务等等出现内存泄漏会导致响应越来越慢最终卡死。 以下是两种内存泄漏的情况 void MemoryLeaks()
{// 1.内存申请了忘记释放int* p1 (int*)malloc(sizeof(int));int* p2 new int;// 2.异常安全问题int* p3 new int[10];Func(); // 这里Func函数抛异常导致 delete[] p3未执行p3没被释放delete[] p3;
}3.2 内存泄漏的分类 C/C程序中一般我们关心两种方面的内存泄漏 堆内存泄漏Heap leak 堆内存指的是程序执行中依据须要分配通过malloc/calloc/realloc/new等从堆中分配的一块内存用完后必须通过调用相应的free或者delete删掉。假设程序的设计错误导致这部分内存没有被释放那么以后这部分空间将无法再被使用就会产生Heap Leak。 系统资源泄漏 指程序使用系统分配的资源比方套接字、文件描述符、管道等没有使用对应的函数释放掉导致系统资源的浪费严重可导致系统效能减少系统执行不稳定。 3.3 如何检测内存泄漏 在linux下内存泄漏检测Linux下几款内存泄露检查工具。 在windows下使用第三方工具VLD(Visual LeakDetector)内存泄露库。 其他工具内存泄漏工具比较内存泄露检测工具比较。 3.4 如何避免内存泄漏 工程前期良好的设计规范养成良好的编码规范申请的内存空间记着匹配的去释放。这个理想状态。但是如果碰上异常时就算注意释放了还是可能会出问题。需要下一条智能指针来管理才有保证。 采用RAII思想或者智能指针来管理资源。 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。 出问题了使用内存泄漏工具检测。不过很多工具都不够靠谱或者收费昂贵。 总结 内存泄漏非常常见解决方案分为两种1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。 4. 智能指针的使用及原理
4.1 RAII思想 RAIIResource Acquisition Is Initialization是一种利用对象生命周期来控制程序资源如内存、文件句柄、网络连接、互斥量等等的简单技术。 在对象构造时获取资源接着控制对资源的访问使之在对象的生命周期内始终保持有效最后在对象析构的时候释放资源。借此我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处 不需要显式地释放资源。采用这种方式对象所需的资源在其生命期内始终保持有效。 使用RAII思想设计的SmartPtr类 templateclass T
class SmartPtr
{
public:SmartPtr(T* ptr nullptr): _ptr(ptr){}~SmartPtr(){if (_ptr)delete _ptr;}
private:T* _ptr;
};
int div()
{int a, b;cin a b;if (b 0)throw invalid_argument(除0错误);return a / b;
}
void Func()
{SmartPtrint sp1(new int);SmartPtrint sp2(new int);cout div() endl;
}
int main()
{try {Func();}catch (const exception e){cout e.what() endl;}return 0;
}4.2 智能指针的原理 上述的SmartPtr还不能将其称为智能指针因为它还不具有指针的行为。指针可以解引用也可以通过-去访问所指空间中的内容因此AutoPtr模板类中还得需要将* 、-重载下才可让其像指针一样去使用。 templateclass T
class SmartPtr
{
public:SmartPtr(T* ptr nullptr): _ptr(ptr){}~SmartPtr(){if (_ptr)delete _ptr;}T operator*() { return *_ptr; }T* operator-() { return _ptr; }
private:T* _ptr;
};
struct Date
{int _year;int _month;int _day;
};
int main()
{SmartPtrint sp(new int);*sp 10;SmartPtrDate sparray(new Date);sparray-_year 2024;sparray-_month 1;sparray-_day 1;return 0;
}智能指针的原理 RAII特性。重载operator*和opertaor-具有像指针一样的行为。 4.3 C智能指针发展历史 思考一下前面我们简单实现的智能指针存在一个大坑。那就是当一个指针拷贝构造或者赋值给另外一个指针时由于我们想让两个指针指向同一块空间所以我们实现的是浅拷贝但是指向了同一块空间就会有析构函数调用两次的风险。由于这一个大坑智能指针进行了很多次优化更迭。 在C98的时候其实就已经在库中实现了智能指针也就是第一个智能指针**auto_ptr**。 既然有析构两次的风险那么auto_ptr在每一次拷贝构造或赋值后直接将自己置空这样实现了管理权转移。但是对于不了解auto_ptr的人来说使用它无疑是巨大的风险。 因此在很长一段时间里程序员都习惯于使用boost库中的智能指针scoped_ptr、shared_ptr和weak_ptr。C11在boost库的基础上完善并推出了unique_ptr、shared_ptr和weak_ptr这与以上boost库中的三个智能指针相对应。 接下来我们来看看这些智能指针的功能和区别。 4.4 std::auto_ptr std::auto_ptr文档 C98版本的库中就提供了auto_ptr的智能指针。下面演示auto_ptr的使用及问题。 auto_ptr的实现原理管理权转移的思想下面简化模拟实现了一份my_ptr::auto_ptr来了解它的原理。 // C98 管理权转移 auto_ptr
namespace my_ptr
{templateclass Tclass 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;};
}int main()
{std::auto_ptrint sp1(new int);std::auto_ptrint sp2(sp1); // 管理权转移// sp1悬空*sp2 10;cout *sp2 endl;cout *sp1 endl; // errorreturn 0;
}auto_ptr是一个失败设计很多公司明确要求不能使用auto_ptr。 4.5 std::unique_ptr unique_ptr文档 unique_ptr的实现原理独占所有权简单粗暴的防拷贝下面简化模拟实现了一份my_ptr::unique_ptr来了解它的原理。 namespace my_ptr
{templateclass Tclass unique_ptr{public: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_ptrT operator(const unique_ptrTsp) delete;private:T* _ptr;};
}
int main()
{my_ptr::unique_ptrint sp1(new int);//my_ptr::unique_ptrint sp2(sp1); // errorstd::unique_ptrint sp1(new int);//std::unique_ptrint sp2(sp1); // errorreturn 0;
}unique_ptr直接禁用了拷贝和赋值独占指针所有权在一些场景下还是可以用到它的。 4.6 std::shared_ptr std::shared_ptr文档 shared_ptr是实际运用中使用最多的智能指针通过引用计数支持拷贝赋值非常优雅 shared_ptr的原理是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。 shared_ptr在其内部给每个资源都维护了着一份计数用来记录该份资源被几个对象共享。 在对象被销毁时也就是析构函数调用就说明自己不使用该资源了对象的引用计数减一。 如果引用计数是0就说明自己是最后一个使用该资源的对象必须释放该资源。 如果不是0就说明除了自己还有其他对象在使用该份资源不能释放该资源否则其他对象就成野指针了。 namespace my_ptr
{templateclass Tclass shared_ptr{public:// RAIIshared_ptr(T* ptr nullptr):_ptr(ptr), _pcount(new int(1)){}void release(){if (--(*_pcount) 0){//cout delete- _ptr endl;delete _ptr;delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptrT sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount);}// sp1 sp3shared_ptrT operator(const shared_ptrT sp){if (_ptr ! sp._ptr){release();_ptr sp._ptr;_pcount sp._pcount;(*_pcount);}return *this;}// 像指针一样T operator*(){return *_ptr;}T* operator-(){return _ptr;}int use_count() const{return *_pcount;}T* get() const{return _ptr;}private:T* _ptr;int* _pcount;};
}4.7 shared_ptr的循环引用问题 请先分析以下代码 struct ListNode
{int _data;my_ptr::shared_ptrListNode _prev;my_ptr::shared_ptrListNode _next;~ListNode() { cout ~ListNode() endl; }
};
int main()
{my_ptr::shared_ptrListNode node1(new ListNode);my_ptr::shared_ptrListNode 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;
}输出结果 可以看到这里并未调用ListNode的析构函数为什么呢 循环引用分析 node1和node2两个智能指针对象指向两个节点引用计数变成1我们不需要手动delete。 node1的_next指向node2node2的_prev指向node1引用计数变成2。 node1和node2析构引用计数减到1但是_next还指向下一个节点。但是_prev还指向上一个节点。 也就是说_next析构了node2就释放了。 也就是说_prev析构了node1就释放了。 但是_next属于node1的成员node1释放了_next才会析构而node1由_prev管理_prev属于node2成员所以这就叫循环引用谁也不会释放。 图解 那么我们如何解决此问题呢 这里我们就来认识一下专门用来配合shared_ptr使用的指针——weak_ptr。 先来看一下它的简单实现版本 namespace my_ptr
{// 简化版本的weak_ptr实现templateclass Tclass weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptrT sp):_ptr(sp.get()){}weak_ptrT operator(const shared_ptrT sp){_ptr sp.get();return *this;}T operator*(){return *_ptr;}T* operator-(){return _ptr;}private:T* _ptr;};
}可以看到weak_ptr用于获得shared_ptr管理的资源的非拥有访问权。通过 weak_ptr可以检查资源是否存在weak_ptr不会增加资源的引用计数。 因此上述问题的解决方案就是在引用计数的场景下把节点中的_prev和_next改成weak_ptr就可以了。 struct ListNode
{int _data;my_ptr::weak_ptrListNode _prev;my_ptr::weak_ptrListNode _next;~ListNode() { cout ~ListNode() endl; }
};
int main()
{my_ptr::shared_ptrListNode node1(new ListNode);my_ptr::shared_ptrListNode 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;时weak_ptr的_next和_prev不会增加node1和node2的引用计数很好的解决了这里的问题。 4.8 定制删除器 当使用智能指针时会出现下面问题 shared_ptrint sp1(new int[10]);当调用sp1的析构函数时默认调用的是delete而new []应该调用delete []我们该如何告诉编译器要使用的delete方式呢 shared_ptr的构造函数有一个模板参数类型D可以让我们传参要调用的删除方法。 因此我们可以这样使用 shared_ptrint sp2(new int[10], [](int* ptr) { delete[] ptr; });
shared_ptrFILE sp3(fopen(test.txt, r), [](FILE* ptr) { fclose(ptr); });拓展 除了会用让我们想想shared_ptr该如何实现 我们可以通过function包装器来定义成员函数去接收构造函数中的删除器参数。 namespace my_ptr
{templateclass Tclass shared_ptr{public:// RAIIshared_ptr(T* ptr nullptr):_ptr(ptr), _pcount(new int(1)){}templateclass Dshared_ptr(T* ptr, D del): _ptr(ptr), _pcount(new int(1)), _del(del){}// functionvoid(T*) _del;void release(){if (--(*_pcount) 0){//cout delete- _ptr endl;//delete _ptr;_del(_ptr);delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptrT sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount);}// sp1 sp3shared_ptrT operator(const shared_ptrT sp){if (_ptr ! sp._ptr){release();_ptr sp._ptr;_pcount sp._pcount;(*_pcount);}return *this;}// 像指针一样T operator*(){return *_ptr;}T* operator-(){return _ptr;}int use_count() const{return *_pcount;}T* get() const{return _ptr;}private:T* _ptr;int* _pcount;functionvoid(T*) _del [](T* ptr) {delete ptr; };};
}