建网站什么网最好,如何拥有自己的专属域名,浙江网站建设制作,wangye目录
1. 为什么需要智能指针#xff1f;
2. 内存泄漏
2.1 什么是内存泄漏#xff0c;内存泄漏的危害
2.2 内存泄漏分类#xff08;了解#xff09;
堆内存泄漏(Heap leak)
系统资源泄漏
2.3 如何检测内存泄漏#xff08;了解#xff09;
2.4如何避免内存泄漏
3.…
目录
1. 为什么需要智能指针
2. 内存泄漏
2.1 什么是内存泄漏内存泄漏的危害
2.2 内存泄漏分类了解
堆内存泄漏(Heap leak)
系统资源泄漏
2.3 如何检测内存泄漏了解
2.4如何避免内存泄漏
3.智能指针的使用及原理
3.1 RAII
// 使用RAII思想设计的SmartPtr类
编辑
std::auto_ptr
模拟实现
std::unique_ptr
编辑
编辑
模拟实现
编辑
std::shared_ptr
模拟实现 赋值(重要)
总代码
循环引用问题
解决方法(weak_ptr不是智能指针)
模拟实现
测试:编辑
make_shared
功能
语法
优点
示例
注意事项
定制删除器(用到了包装器) 题目 1. 为什么需要智能指针 下面我们先分析一下下面这段程序有没有什么内存方面的问题提示一下注意分析MergeSort函数中的问题。
因为div抛异常后会跳过delete导致内存泄漏
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;
}2. 内存泄漏
2.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;
}
2.2 内存泄漏分类了解
C/C程序中一般我们关心两种方面的内存泄漏
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一 块内存用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分 内存没有被释放那么以后这部分空间将无法再被使用就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源比方套接字、文件描述符、管道等没有使用对应的函数释放 掉导致系统资源的浪费严重可导致系统效能减少系统执行不稳定。
2.3 如何检测内存泄漏了解
在linux下内存泄漏检测Linux下几款C程序中的内存泄露检查工具_c内存泄露工具分析-CSDN博客
在windows下使用第三方工具VS编程内存泄漏VLD(Visual LeakDetector)内存泄露库_visual leak detector vs2020-CSDN博客
其他工具内存泄露检测工具比较 - 默默淡然 - 博客园
2.4如何避免内存泄漏
1. 工程前期良好的设计规范养成良好的编码规范申请的内存空间记着匹配的去释放。ps 这个理想状态。但是如果碰上异常时就算注意释放了还是可能会出问题。需要下一条智 能指针来管理才有保证。
2. 采用RAII思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。ps不过很多工具都不够靠谱或者收费昂贵。 总结一下:
内存泄漏非常常见解决方案分为两种1、事前预防型。如智能指针等。2、事后查错型。如泄 漏检测工具。
3.智能指针的使用及原理
3.1 RAII
RAIIResource Acquisition Is Initialization是一种利用对象生命周期来控制程序资源如内 存、文件句柄、网络连接、互斥量等等的简单技术。
在对象构造时获取资源接着控制对资源的访问使之在对象的生命周期内始终保持有效最后在 对象析构的时候释放资源。借此我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处
不需要显式地释放资源。
采用这种方式对象所需的资源在其生命期内始终保持有效。
// 使用RAII思想设计的SmartPtr类 int div()
{int a, b;cin a b;if (b 0)throw invalid_argument(除0错误);return a / b;
}
//这样就不用担心没有释放了
templateclass T
class SmartPtr
{
public:// RAII// 资源交给对象管理对象生命周期内资源有效对象生命周期到了释放资源// 1、RAII管控资源释放// 2、像指针一样SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout delete: _ptr endl;delete _ptr;}T operator*(){return *_ptr;}T* operator-(){return _ptr;}
private:T* _ptr;
};
void f()
{//new也可能有很小的概率出问题SmartPtrpairstring, string sp1(new pairstring, string(1111, 22222));//这里div抛异常后sp1会释放然后直接跳过进入main中不进入,sp2,sp3,只要是f结束f中的new后都//会调用deletediv();SmartPtrpairstring, string sp2(new pairstring, string);SmartPtrpairstring, string sp3(new pairstring, string);SmartPtrstring sp4(new string(xxxxx));/*cout *sp4 endl;cout sp1-first endl;cout sp1-second endl;*///这里div抛异常后sp1和sp2sp3都会释放div();//delete p1;//cout delete: p1 endl;
}
int main()
{try{f();}catch (const exception e){cout e.what() endl;}catch (...){cout Unkown Exception endl;}return 0;
} 没写赋值浅拷贝深拷贝也没法解决问题把sp2的值给了sp1只释放了一个还有一个没释放发生了内存泄漏 c中new了要delete
有了异常以后已经不可控了因为抛异常会影响执行流会改变程序执行顺序 智能指针 using namespace std;
int div()
{int a, b;cin a b;if (b 0)throw invalid_argument(除0错误);return a / b;
}
//这样就不用担心没有释放了
templateclass T
class SmartPtr
{
public:// RAII// 资源交给对象管理对象生命周期内资源有效对象生命周期到了释放资源// 1、RAII管控资源释放// 2、像指针一样SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout delete: _ptr endl;delete _ptr;}T operator*(){return *_ptr;}T* operator-(){return _ptr;}
private:T* _ptr;
};
void f()
{//new也可能有很小的概率出问题SmartPtrpairstring, string sp1(new pairstring, string(1111, 22222));//这里div抛异常后sp1会释放然后直接跳过进入main中不进入,sp2,sp3,只要是f结束f中的new后都//会调用deletediv();SmartPtrpairstring, string sp2(new pairstring, string);SmartPtrpairstring, string sp3(new pairstring, string);SmartPtrstring sp4(new string(xxxxx));/*cout *sp4 endl;cout sp1-first endl;cout sp1-second endl;*///这里div抛异常后sp1和sp2sp3都会释放div();//delete p1;//cout delete: p1 endl;}int main()
{try{f();}catch (const exception e){cout e.what() endl;}catch (...){cout Unkown Exception endl;}return 0;
}
std::auto_ptr
文档 https://cplusplus.com/reference/memory/auto_ptr/
auto_ptr是一个失败设计很多公司明确要求不能使用auto_ptr
会导致悬空问题实质上是管理权限的转移
C98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。
auto_ptr的实现原理管理权转移的思想下面简化模拟实现了一份bit::auto_ptr来了解它的原 理
// C98 管理权转移 auto_ptr int main()
{//这种形式跟上面自己写的Smartptr类似auto_ptrint at1(new int);//这个auto_ptr拷贝后会出现大问题会出现悬空问题auto_ptrint at2(at1);//管理权限的转移*at2 10;cout *at2 endl;//*at1 10;会报错因为*at1已经悬空*at1 10;cout *at1 endl;return 0;
}
模拟实现 namespace Ljw
{templateclass Tclass auto_ptr{public:auto_ptr(T* ptr):_ptr(ptr){}~auto_ptr(){cout ~auto_ptr endl;if (_ptr){delete _ptr;}}T operator*(){return *_ptr;}T* operator-(){return _ptr;}//重点在于拷贝auto_ptr(auto_ptrT ptr):_ptr(ptr._ptr){//这里就是悬空了管理权转ptr._ptr nullptr;}private:T* _ptr;};
}std::unique_ptr C11中开始提供更靠谱的unique_ptr
文档https://cplusplus.com/reference/memory/unique_ptr/
unique_ptr的实现原理简单粗暴的防拷贝下面简化模拟实现了一份UniquePtr来了解它的原 理
/ C11库才更新智能指针实现
// C11出来之前boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr // C11将boost库中智能指针精华部分吸收了过来
// C11-unique_ptr/shared_ptr/weak_ptr // unique_ptr/scoped_ptr // 原理简单粗暴 -- 防拷贝
模拟实现 templateclass T
class unique_ptr
{
public://默认构造unique_ptr(T*ptr):_ptr(ptr){}~unique_ptr(){delete _ptr;}T operator*(){return *_ptr;}T* operator-(){return _ptr;}//直接用delete禁止拷贝构造unique_ptr(unique_ptrT ptr) delete;//直接用delete禁止赋值unique_ptrT operator(unique_ptrT ptr) delete;
private:T* _ptr;
};
std::shared_ptr
C11中开始提供更靠谱的并且支持拷贝的shared_ptr
文档https://cplusplus.com/reference/memory/shared_ptr/
shared_ptr的原理是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。例如 老师晚上在下班之前都会通知让最后走的学生记得把门锁下。
1. shared_ptr在其内部给每个资源都维护了着一份计数用来记录该份资源被几个对象共 享。
2. 在对象被销毁时(也就是析构函数调用)就说明自己不使用该资源了对象的引用计数减 一。
3. 如果引用计数是0就说明自己是最后一个使用该资源的对象必须释放该资源
4. 如果不是0就说明除了自己还有其他对象在使用该份资源不能释放该资源否则其他对象就成野指针了。
// 引用计数支持多个拷贝管理同一个资源最后一个析构对象释放资源
原理图 模拟实现
用指针创建空间进行计数保存是合理的静态全局的也是不符合的。 重点 赋值(重要) 步骤1先把sp1和sp3弄成共同资源,先把sp2和sp4和sp5弄成共同资源 经过测试后实际只有释放一个空间
//赋值
//要考虑到前后计数要改变
//如果到0了也是需要直接释放的
//如果是同一块资源赋值可以不用处理
//同一块资源的判定条件是
shared_ptrT operator(shared_ptrT ptr)
{//同一块资源if (_ptr ptr._ptr)return *this;//赋值的时候,this的空间计数就少了1(*_count)--;if (*(_count) 0){delete _ptr;delete _count;}//先增加计数也一样然后ptr的空间资源计数就多了1//(*ptr._count);//赋值_ptr ptr._ptr;_count ptr._count;//然后ptr的空间资源计数就多了1(*_count);return *this;
}
总代码
templateclass T
class shared_ptr
{
public://默认构造shared_ptr(T* ptrnullptr):_ptr(ptr),_count(new int(1))//这里_count是指针要单独开一个空间,进行管理{}~shared_ptr(){(*_count)--;if ((* _count) 0){cout ~shared_ptr endl;delete _ptr;delete _count;} }T operator*(){return *_ptr;}T* operator-(){return _ptr;}//sp2(sp1)shared_ptr(shared_ptrT ptr):_ptr(ptr._ptr),_count(ptr._count){(*_count);}//赋值//要考虑到前后计数要改变//如果到0了也是需要直接释放的//如果是同一块资源赋值可以不用处理//同一块资源的判定条件是shared_ptrT operator(shared_ptrT ptr){//同一块资源if (_ptr ptr._ptr)return *this;//赋值的时候,this的空间计数就少了1(*_count)--;if (*(_count) 0){delete _ptr;delete _count;}//先增加计数也一样然后ptr的空间资源计数就多了1//(*ptr._count);//赋值_ptr ptr._ptr;_count ptr._count;//然后ptr的空间资源计数就多了1(*_count);return *this;}int use_count() const{return *_count;}T* get() const{return _ptr;}
private:T* _ptr;//计数int* _count;
};
循环引用问题 Node手动释放版本
delete n1上面如果抛异常了呢所以就需要智能指针
这里报错的原因类型不匹配是因为一个自定义类型一个内置类型要把上面的也改成智能指针,所以要改成如下图 正常释放(shared_ptr) 这样就发生了循环引用没法正常释放空间屏蔽其中一个就没问题但两个都有就不行了 解决方法(weak_ptr不是智能指针)
/ 解决方案在引用计数的场景下把节点中的_prev和_next改成weak_ptr就可以了
// 原理就是node1-_next node2;和node2-_prev node1;时weak_ptr的_next和
_prev不会增加node1和node2的引用计数。 模拟实现
templateclass T
class 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;
};
测试:
make_shared
make_shared 是 C11 标准库中引入的一个模板函数它属于 memory 头文件中定义的智能指针工具集。make_shared 的主要目的是简化智能指针的创建过程并可能提供性能上的优化。
以下是 make_shared 的一些关键点
功能
make_shared 用于创建一个 std::shared_ptr 对象该对象管理动态分配的对象。它接受一个类型参数以及该类型构造函数所需的参数并返回一个 shared_ptr 指向新创建的对象。
语法
template class T, class... Args
shared_ptrT make_shared( Args... args );
优点 简洁性make_shared 允许你在一条语句中创建和管理动态分配的对象而不需要分别写 new 和 shared_ptr 的构造函数。 性能优化make_shared 通常比直接使用 new 分配内存再包装成 shared_ptr 更高效。这是因为 make_shared 只需要在堆上分配一次内存即同时为对象本身和共享的引用计数控制块分配空间。而直接使用 new 和 shared_ptr 的构造函数通常需要两次内存分配一次为对象本身另一次为控制块。 异常安全当使用 new 分配内存并将结果传递给 shared_ptr 的构造函数时如果在参数传递过程中发生异常可能会导致内存泄漏。使用 make_shared 可以避免这种风险因为 new 和 shared_ptr 的构造是在一个操作中完成的。
示例
下面是使用 make_shared 创建 std::shared_ptr 的一个例子
#include memory
#include iostreamclass MyClass {
public:MyClass(int a, int b) : x(a), y(b) {}void print() const { std::cout x y std::endl; }private:int x, y;
};int main() {auto mySharedPtr std::make_sharedMyClass(10, 20);mySharedPtr-print(); // 输出: 10 20return 0;
}注意事项
make_shared 不能用于管理动态分配的数组因为这会导致 shared_ptr 的析构函数使用错误的删除器delete 而不是 delete[]。当需要指定自定义的删除器时使用 std::allocate_shared 而不是 make_shared。在某些情况下make_shared 可能不是最佳选择特别是当传递的参数涉及到类型转换或需要绑定引用时。
make_shared 是 C 中管理动态内存时非常有用的工具可以减少代码量并提高性能。然而了解其使用限制和最佳实践是编写高效、安全代码的关键。
定制删除器(用到了包装器)
unique和shared都有定制删除器
因为底层是delete所以[ ]的释放要实现一个删除器 在shared_ptr中总代码
templateclass T
class shared_ptr
{
public://默认构造shared_ptr(T* ptrnullptr):_ptr(ptr),_count(new int(1))//这里_count是指针要单独开一个空间,进行管理{}templateclass Dshared_ptr(T* ptr, D del)//这里就不可以加缺省值nullptr了因为是从右到左的:_ptr(ptr), _count(new int(1))//这里_count是指针要单独开一个空间,进行管理,_del(del){}~shared_ptr(){(*_count)--;if ((* _count) 0){cout ~shared_ptr endl;_del(_ptr);delete _count;} }T operator*(){return *_ptr;}T* operator-(){return _ptr;}//sp2(sp1)shared_ptr(shared_ptrT ptr):_ptr(ptr._ptr),_count(ptr._count){(*_count);}//赋值//要考虑到前后计数要改变//如果到0了也是需要直接释放的//如果是同一块资源赋值可以不用处理//同一块资源的判定条件是shared_ptrT operator(shared_ptrT ptr){//同一块资源if (_ptr ptr._ptr)return *this;//赋值的时候,this的空间计数就少了1(*_count)--;if (*(_count) 0){delete _ptr;delete _count;}//先增加计数也一样然后ptr的空间资源计数就多了1//(*ptr._count);//赋值_ptr ptr._ptr;_count ptr._count;//然后ptr的空间资源计数就多了1(*_count);return *this;}int use_count() const{return *_count;}T* get() const{return _ptr;}
private:T* _ptr;//计数int* _count;//删除器functionvoid(T*) _del [](T* ptr) {delete ptr; };
};
如果不是new出来的对象如何通过智能指针管理呢其实shared_ptr设计了一个删除器来解决这 个问题ps删除器这个问题我们了解一下
// 仿函数的删除器
// 仿函数的删除器templateclass Tstruct FreeFunc {void operator()(T* ptr){cout free: ptr endl;free(ptr);}
};templateclass Tstruct DeleteArrayFunc {void operator()(T* ptr){ cout delete[] ptr endl;delete[] ptr; }
};int main()
{FreeFuncint freeFunc;std::shared_ptrint sp1((int*)malloc(4), freeFunc);DeleteArrayFuncint deleteArrayFunc;std::shared_ptrint sp2((int*)malloc(4), deleteArrayFunc);std::shared_ptrA sp4(new A[10], [](A* p){delete[] p; });std::shared_ptrFILE sp5(fopen(test.txt, w), [](FILE* p)
{fclose(p); });return 0;
} 题目
1
weak_ptr不能单独管理资源必须配合shared_ptr一块使用解决shared_ptr中存在的 循环引用问题
RAII的实现方式就是在构造函数中将资源初始化在析构函数中将资源清理掉
RAII方式管理资源可以有效避免资源泄漏问题
RAII方式管理锁有些场景下可以有效避免死锁问题
2
A.auto_ptr智能指针是在C98版本中已经存在的
B.auto_ptr的多个对象之间不能共享资源
C.auto_ptr的实现原理是资源的转移 3
C11中提供的智能指针都只能管理单个对象的资源没有提供管理一段空间资源的智能指针
A.unique_ptr是C11才正式提出的
C.unique_ptr不能使用其拷贝构造函数
D.unique_ptr的对象之间不能相互赋值 4
有些场景下shared_ptr可能会造成循环引用必须与weak_ptr配合使用
A.shared_ptr是C11才正式提出来的
B.shared_ptr对象之间可以共享资源
D.shared_ptr是借助引用计数的方式实现的
5
weak_ptr不能单独管理资源因为其给出的最主要的原因是配合shared_ptr解决其循环 引用问题
weak_ptr和shared_ptr都是通过引用计数实现但是在底层还是有区别的
weak_ptr的唯一作用就是解决shared_ptr中存在的循环引用问题处理解决shared_ptr的循环引用问题外别无它用