当前位置: 首页 > news >正文

【C++智能指针】

智能指针

  • 为什么使用智能指针?
  • 概念
  • 分类
    • auto_ptr
    • unique_ptr
    • shared_ptr
      • 循环引用
      • weak_ptr

为什么使用智能指针?

考虑以下场景:

void div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{int* ptr=new int;div();delete ptr;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

Func()函数中我们在堆上申请了资源(new int对象),当div()调用抛异常时,程序会根据异常处理机制,找到最近的catch捕获异常,这使得程序没有进行到释放资源就结束(未delete int 对象),进而会导致内存泄漏。长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死,所以需要一定的方法来优化。

异常处理机制

  1. 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理。
  2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
  3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
  4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。

常用的解决方案是:
1、事前预防型。如智能指针等。
2、事后查错型。如泄漏检测工具。

概念

智能指针是一种特殊的指针类型,用于自动管理动态分配的内存。相比于传统的裸指针,智能指针提供了更安全、更方便的内存管理方式。
智能指针的原理主要基于对象的生命周期管理和引用计数机制。通过在自动释放内存和避免悬挂指针方面提供便利,它们可以帮助开发人员更轻松地处理动态内存分配和释放的问题。

RAII(Resource Acquisition Is Initialization)
对象的生命周期控制程序资源的技术,在对象构造时获取资源,在对象析构时释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
1.不需要显式地释放资源。(对象析构了资源就释放了)
2.采用这种方式,对象所需的资源在其生命期内始终保持有效。(资源和对象绑定)

分类

在C++98,有智能指针auto_ptr,但在C++11后,auto_ptr被弃用。标准库中提供了三种常见的智能指针:unique_ptr、shared_ptr和weak_ptr。

auto_ptr

auto_ptr是一种独占所有权的智能指针,实现原理是管理权的转移。当一个auto_ptr将其所有权转移给另一个auto_ptr时,原始的auto_ptr将不再拥有对资源的所有权,即两个auto_ptr不能管理同一份资源。
缺陷:
1.拷贝和赋值操作会改变资源的所有权

auto_ptr<string> p1(new string("hhe"));
auto_ptr<string> p2(new string("aab"));
p1=p2; 
//p2赋值给p1后,p1释放掉管理"hhe"的指针并接收p2交给它的管理"aab"的指针
//p2交付完毕后释放自己内部的指针并置NULL。

2.指针被悬空成野指针容易解引用造成错误

auto_ptr<int> sp1(new int);
auto_ptr<int> sp2(sp1); // 管理权转移
//sp1悬空
*sp2 = 10;
cout << *sp2 << endl;
cout << *sp1 << endl;

自C++11起,该类模板已被弃用,用更严谨的unique_ptr 取代了auto_ptr

unique_ptr

unique_ptr对auto_ptr的缺陷做了修改,和auto_ptr类似,unique_ptr是一种独占所有权的智能指针。它具有以下特点:

  1. 提供了唯一拥有的语义,即同一时间只能有一个unique_ptr指向同一块内存。
  2. 当unique_ptr被销毁时,会自动释放其所拥有的对象。
  3. 不允许多个unique_ptr指针共享同一块内存,避免了悬空指针和内存泄漏的风险。
  4. 无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
unique_ptr<string> p1(new string("hhe"));
unique_ptr<string> p2(new string("aab"));
p1 = p2;					// 禁止左值赋值
unique_ptr<string> p3(p2);	// 禁止左值赋值构造
unique_ptr<string> p3(std::move(p1));
p1 = std::move(p2);	// 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样

下面给出unique_ptr指针的模拟实现:

template<class T>
class unique_ptr
{
public:unique_ptr( T* ptr):_ptr(ptr){}~unique_ptr(){if(_ptr)delete _ptr;}T& operator*()return *_ptr;T* operator->()return _ptr;unique_ptr(const unique_ptr<T>& sp) =delete;unique_ptr<T>& operator =(const unique_ptr<T>& sp) =delete;
private:T* _ptr;
}

shared_ptr

为了解决一份资源只能被一个智能指针管理的问题,新增了shared_ptr智能指针。shared_ptr是一种共享所有权的智能指针。它使用引用计数的方式来跟踪对象的引用数量。每次创建shared_ptr时,引用计数增加;每次销毁或重置shared_ptr时,引用计数减少。当引用计数变为0时,shared_ptr会自动释放所管理的对象的内存。

引用计数
因为shared_ptr支持一份资源被多个指针管理,未避免对一块空间资源的重复释放,shared_ptr不必在每次析构时都释放资源,而是在管理该资源的最后一个shared_ptr析构时释放资源。那如何知道一个资源被几个shared_ptr管理?析构时如何知道该指针是最后一个管理该资源的指针?这些问题需要在被管理的资源对象上装一个计数器,供这些shared_ptr共享。
在这里插入图片描述

当创建shared_ptr时,会同时创建一个控制块,并将所管理的对象指针和引用计数等信息存储在该控制块中。多个shared_ptr可以共享同一个控制块,通过引用计数来追踪所有引用该对象的shared_ptr个数。

template<class T>class shared_ptr{shared_ptr(T* ptr=nullptr) :_ptr(ptr), _pcount(new int(1)){}template<class D>shared_ptr(T* ptr, D del) :_ptr(ptr), _pcount(new int(1)) ,_del(del){}~shared_ptr(){if (--(*_pcount) == 0){cout << "delete:" << _ptr << endl;del(_ptr);delete _pcount;}	}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}shared_ptr(const shared_ptr<T>& sp) {_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (this != &sp){--(*_pcount);if (*_pcount == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;*_pcount++;}return *this;}int use_count(){return *_pcount;}T* get(){return _ptr;}private:T* _ptr;int* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr;};};

循环引用

shared_ptr在使用不当容易引发循环引用问题。循环引用就是一组对象彼此持有对方的智能指针,从而导致它们的引用计数永远不会变为零。当存在循环引用时,智能指针可能会导致内存泄漏,因为循环引用会阻止引用计数减到零。这将导致相关的内存资源无法被释放,从而造成内存泄漏。
下面是一个产生了循环引用问题的示例:

struct Father
{
...//此处省略成员变量及函数shared_ptr<Child> child;
}
struct Child
{
...//此处省略成员变量及函数shared_ptr<Father> father;
}
//调用
shared_ptr<Father> a(new Father);
shared_ptr<Child> b(new Child);
a->child=b;
b->father=a;

循环计数情况如下图所示:
在这里插入图片描述

weak_ptr

weak_ptr是一种弱引用的智能指针,通过使用weak_ptr作为循环引用中的其中一个对象,可以打破循环引用并允许相关对象被正确地销毁。它可以观测shared_ptr所管理的对象,并不会增加引用计数。当最后一个shared_ptr被销毁时,即使还有weak_ptr存在,也不会阻止所管理对象的内存释放。(不参与资源的管理,解决智能指针交叉使用问题)

struct Father
{
...//此处省略成员变量及函数weak_ptr<Child> child;
}
struct Child
{
...//此处省略成员变量及函数weak_ptr<Father> father;
}

使用weak_ptr可以有效避免循环引用问题。

http://www.lryc.cn/news/214266.html

相关文章:

  • gcc/g++使用格式+各种选项,预处理/编译(分析树,编译优化,生成目标代码)/汇编/链接过程(函数库,动态链接)
  • OSPF复习(2)
  • FPGA时序分析与约束(9)——主时钟约束
  • sqlite3 关系型数据库语言 SQL 语言
  • spring boot中的多环境配置
  • python3 阿里云api进行巡检发送邮件
  • 【Linux】安装使用Nginx负载均衡,并且部署前端项目
  • k8s中 pod 或节点的资源利用率监控
  • 订水商城实战教程07-搜索
  • stm32内 misc stm32f10x_hd stm32f10x_it stm32f10x_conf关系
  • 树结构及其算法-二叉查找树
  • PHP自定义文件缓存实现
  • 猫耳 Android 播放框架开发实践
  • linux下df -h 命令一直卡住的解决方法
  • 系统架构设计热点知识
  • 2023-在mac下安装Homebrew的国内镜像
  • Ubuntu 20.04设置虚拟内存 (交换内存swap)解决内存不足
  • RabbitMQ-死信交换机和死信队列
  • [HNCTF 2022 WEEK2]easy_include 文件包含遇上nginx
  • python中transform和apply的区别是什么
  • TCP 协议
  • Azure机器学习 - 在 Azure 机器学习中上传、访问和浏览数据
  • 新建包含cuda和cudnn的docker
  • Opensips安装配置(以下操作均已centOS 6.3系统为准)
  • 第03章 用户与权限管理
  • 赋能制造业高质量发展,释放采购数字化新活力——企企通亮相武汉2023国际智能制造创新论坛
  • 洗地新天花板:CEYEE希亦顶配机皇T800 Pro洗地机多点发力上市开售
  • 如何创建一个react项目
  • 面试算法49:从根节点到叶节点的路径数字之和
  • http1,https,http2,http3总结