C++ 智能指针

C++ 智能指针

手动new的缺陷

我们如果自己new堆内存,需要我们自己在合适的地方释放内存,如果忘记了就会导致内存泄露。这在大型项目里面是很严重的问题,如果是在不常调用的地方内存泄露,那么可能会在服务器启动的后几周几月突然崩溃,如果是在常调用的地方,可能几天几周就奔溃,会带来很大的经济损失。因此在公司不要写出内存泄露的代码。

如果你确实有很强的delete意识,但是在一些复杂的情况下,再很强也会有疏忽的,例如在抛异常的情况下。你先new了n个对象,然后中途还没delete就抛异常了,那么你得在抛异常里面释放内存,如果这个异常提前抛到其他地方了,还得另外算。所以有没有什么方式让内存交给系统管理。

析构函数管理资源释放

因此想到了了析构函数的特点,析构函数在当前类生命周期结束后会自动执行其析构函数,如果我们创建一个类让其管理我们创建的指针,那么在当前作用域结束后就会自动释放内存了

#include<iostream> using namespace std; template<class T> class autoptr { using Ptr = T*; using Ref = T&; public: autoptr(T*ptr) :_ptr(ptr) {} ~autoptr() { if(_ptr){ cout << "指针内存释放" << endl; delete _ptr; } } private: Ptr _ptr; }; int main() { { autoptr<int>ptr(new int); } cout << "作用域结束" << endl; return 0; }

效果不错,我们还可以给其加上指针的操作,这样我们可以把类当成指针操作。

template<class T> class autoptr { using Ptr = T*; using Ref = T&; public: autoptr(T*ptr=nullptr) :_ptr(ptr) {} ~autoptr() { if (_ptr) { cout << "指针内存释放" << endl; delete _ptr; } } Ref operator*()const { if (!_ptr)throw std::runtime_error("空指针"); return *_ptr; } Ptr operator->()const noexcept { return _ptr; } operator bool()const{ return _ptr != nullptr; } private: Ptr _ptr=nullptr; };

测试一波:

非常优雅

作用域问题

随之而来问题,我如果在当前作用域外还要用咋办?

当前创建的类是没有救了,它无法出作用域,因此指针窜在它手里必定会被析构,因此我们需要让其他对象来传承我们的底层指针。

那么我们顺便带来官方库对智能指针的实现

因此有了第一种方法,也是std的auto_ptr

auto_ptr

它的方式是,当实现拷贝赋值时,会将底层的指针交给对方,自己置空

template<class T> class autoPtr { using Ptr = T*; using Ref = T&; public: explicit autoPtr(T*ptr=nullptr) :_ptr(ptr) {} ~autoPtr() noexcept { if (_ptr) { cout << "指针内存释放" << endl; delete _ptr; } } autoPtr(autoPtr& x) { _ptr = x._ptr; x._ptr = nullptr; } autoPtr& operator=(autoPtr& x) noexcept { if (&x != this) { _ptr = x._ptr; x._ptr = nullptr; } return *this; } Ref operator*()const { if (!_ptr)throw std::runtime_error("空指针"); return *_ptr; } Ptr operator->()const noexcept { return _ptr; } explicit operator bool()const noexcept { return _ptr != nullptr; } private: Ptr _ptr=nullptr; }; 

测试一下

int main() { autoPtr<pair<int, string>>p; { autoPtr<pair<int,string>>ptr(new pair<int,string>); *ptr = { 1,"nihao" }; cout << ptr->second << endl; p = ptr; try { *ptr; } catch (const std::runtime_error& x) { cout << x.what() << endl; } } cout << "作用域结束" << endl; cout << p->second << endl; return 0; }

unique_ptr

某些情况我们只希望当前的堆内存只被一个对象所管控,因此有了unique_ptr。

template<class T> class uniquePtr { using Ptr = T*; using Ref = T&; public: explicit uniquePtr(T* ptr = nullptr) :_ptr(ptr) { } ~uniquePtr() noexcept { if (_ptr) { cout << "指针内存释放" << endl; delete _ptr; } } uniquePtr(uniquePtr&& x)noexcept { if (this != &x) { swap(x); } } uniquePtr(const uniquePtr&) noexcept = delete; uniquePtr& operator=(const uniquePtr& ) noexcept = delete; uniquePtr& operator=(uniquePtr&& x) noexcept { if (this != &x) { swap(x); } return *this; } Ref operator*()const { if (!_ptr)throw std::runtime_error("空指针"); return *_ptr; } Ptr operator->()const noexcept { return _ptr; } explicit operator bool()const noexcept { return _ptr != nullptr; } Ptr get()const noexcept { return _ptr; } Ptr release()noexcept {//主动放弃对指针的管理 Ptr ret = _ptr; _ptr = nullptr; return ret; } void reset(Ptr ptr = nullptr)noexcept {//释放旧内存,管理新内存 if (_ptr)delete _ptr; _ptr = ptr; } void swap(uniquePtr& x)noexcept { std::swap(_ptr, x._ptr); } private: Ptr _ptr = nullptr; };

shared_ptr

auto_ptr这种剥夺式的赋值不被采用,会导致之前的指针被悬空,很容易出问题,而且同一块地址是可以被很多指针存储的,这种剥夺式的方式并不符合开发的需求,因此auto_ptr在C++11被禁止,在C++17彻底被删除

因此有了后来的shared_ptr,shared_ptr在进行拷贝的时候并不会转移指针,而是拷贝底层的地址。并通过一个计数来判断类析构时是否delete调我们的内存

template<class T> class sharedPtr { using Ptr = T*; using Ref = T&; public: explicit sharedPtr(T* ptr = nullptr) :_ptr(ptr) ,_pcount(ptr?new size_t(1):nullptr) { } ~sharedPtr() noexcept { if (_ptr && !(--*_pcount)) { cout << "指针内存释放" << endl; delete _ptr; delete _pcount; } } sharedPtr(sharedPtr&& x)noexcept { if (this != &x) { swap(x); } } sharedPtr(const sharedPtr& x) noexcept { _ptr = x._ptr; _pcount = x._pcount; if(_ptr)++*_pcount; } sharedPtr& operator=(const sharedPtr& x) noexcept { if (&x != this) { sharedPtr temp(x); swap(temp); } return *this; } sharedPtr& operator=(sharedPtr&& x) noexcept { if (this != &x) { swap(x); } return *this; } Ref operator*()const { if (!_ptr)throw std::runtime_error("空指针"); return *_ptr; } Ptr operator->()const noexcept { return _ptr; } explicit operator bool()const noexcept { return _ptr != nullptr; } Ptr get()const noexcept { return _ptr; } size_t use_count()const noexcept { if (!_ptr)return 0; return *_pcount; } private: void swap(sharedPtr& x)noexcept { std::swap(_ptr, x._ptr); std::swap(_pcount, x._pcount); } Ptr _ptr = nullptr; size_t* _pcount = nullptr; };

多线程问题

如果多个线程都在用当前指针,那么count必须是原子操作或者加锁,这里采用原子操作

#include<atomic> template<class T> class sharedPtr { using Ptr = T*; using Ref = T&; public: explicit sharedPtr(T* ptr = nullptr) :_ptr(ptr) ,_pcount(ptr?new std::atomic<size_t>(1):nullptr) { } ~sharedPtr() noexcept { if (_ptr && !(--*_pcount)) { cout << "指针内存释放" << endl; delete _ptr; delete _pcount; } } sharedPtr(sharedPtr&& x)noexcept { if (this != &x) { swap(x); } } sharedPtr(const sharedPtr& x) noexcept { _ptr = x._ptr; _pcount = x._pcount; if(_ptr)++*_pcount; } sharedPtr& operator=(const sharedPtr& x) noexcept { if (&x != this) { sharedPtr temp(x); swap(temp); } return *this; } sharedPtr& operator=(sharedPtr&& x) noexcept { if (this != &x) { swap(x); } return *this; } Ref operator*()const { if (!_ptr)throw std::runtime_error("空指针"); return *_ptr; } Ptr operator->()const noexcept { return _ptr; } explicit operator bool()const noexcept { return _ptr != nullptr; } Ptr get()const noexcept { return _ptr; } size_t use_count()const noexcept { if (!_ptr)return 0; return *_pcount; } private: void swap(sharedPtr& x)noexcept { std::swap(_ptr, x._ptr); std::swap(_pcount, x._pcount); } Ptr _ptr = nullptr; std::atomic<size_t>* _pcount = nullptr; }; 

weak_ptr

我们看看以下的情况

template<class T> struct listNode { listNode(const T&val) :_val(val) {} sharedPtr<listNode<T>>_l, _r; T _val; }; int main() { sharedPtr<listNode<int>>ln1(new listNode(1)), ln2(new listNode(1)); ln1->_r = ln2; ln2->_l = ln1; return 0; }

最后程序退出的时候

ln1和ln2是在main函数作用域的,因此会释放析构。最后会变成以下情况

产生的原因是listNode是通过new出来的,其内存处在堆区。导致两个内存都不会自己释放,这就导致两个处在堆区的shared_ptr不会count--,最终导致内存泄露。

本质原因是堆内存的shared_ptr的count计数导致内存泄露

那么我们可以实现一个weak_ptr指针,只存指针不做引用计数

template<class T> class weakPtr { using Ptr = T*; using Ref = T&; public: explicit weakPtr(const sharedPtr<T>&sp=sharedPtr<T>()) :_ptr(sp.get()) { } ~weakPtr() noexcept { } weakPtr(weakPtr&& x)noexcept { if (this != &x) { swap(x); } } weakPtr(const weakPtr& x) noexcept { _ptr = x._ptr; } weakPtr& operator=(const sharedPtr<T>& x) noexcept { weakPtr temp(x); swap(temp); return *this; } weakPtr& operator=(const weakPtr& x) noexcept { if (&x != this) { weakPtr temp(x); swap(temp); } return *this; } weakPtr& operator=(weakPtr&& x) noexcept { if (this != &x) { swap(x); } return *this; } Ref operator*()const { if (!_ptr)throw std::runtime_error("空指针"); return *_ptr; } Ptr operator->()const noexcept { return _ptr; } explicit operator bool()const noexcept { return _ptr != nullptr; } Ptr get()const noexcept { return _ptr; } private: void swap(weakPtr& x)noexcept { std::swap(_ptr, x._ptr); } Ptr _ptr = nullptr; };

这样就可以解决循环引用的问题了

删除器

目前我们的智能指针智能对单个对象进行内存管理,如果new的是数组就会出现问题。new []和最后 delete将会导致未定义行为。因此我们需要加一个删除器

删除器使用

//仿函数方式 struct del { void operator()(int* p) { cout << "仿函数删除器 " << endl; delete p; } }; std::shared_ptr<int>s(new int,del()); std::unique_ptr<int, del>u(new int); //lambda表达式方式 auto del_ = [](int* p) {cout << "lamba 删除器" << endl; delete p; }; std::shared_ptr<int>ls(new int, del_); std::unique_ptr<int, decltype(del_)>lu(new int, del_);

对于shared_ptr,这两种传参是差不多的,但是对于unique_ptr就不一样,unique_ptr需要传删除器变量类型,因为lambda表达式没有默认构造,因此我们需要显示传删除器对象

强引用 弱引用

但是上述所说的shared_ptr和weak_ptr并不是工程实现的版本,还存在一定的问题。那就是weak_ptr不知道存在的指针是否还在,如果直接访问了释放的内存,那么就会抛异常出错。因此有了以下的实现方式:

我让weak_ptr和shared_ptr都持有引用,但是只有shared_ptr可以修改引用,weak_ptr不能修改只能读。我weak_ptr要读取指针的时候,我先访问引用看是否为零,再去访问底层的ptr。

看似上面可以,但是实际是不行的,因为引用也是一个堆内存,当ptr释放后,引用的堆内存会跟着释放,因此我们需要让这个内存不要和ptr一起释放。因此我们有了弱引用。我们创建一个计数器,里面同时存储弱引用和强引用,通过弱引用记录有多少个weak_ptr还在观察计数器,计数器当且仅当强引用和弱引用都为0的时候才会释放。这样就避免了weak_ptr在观察的时候计数器被释放了的问题。

具体代码我就不实现了。

Read more

高性能计算 FPGA 开发:Quartus Prime 18.0 下载安装教程 高带宽内存(HBM2)支持

简介 Quartus Prime 18.0 软件详情Quartus Prime 是英特尔(原 Altera)推出的 PLD/FPGA 设计开发平台,广泛应用于芯片设计、逻辑电路开发、高速接口和嵌入式系统领域。它支持完整的硬件设计流程,从架构设计、逻辑综合、仿真分析,到烧录部署。 * 部分重新配置升级:提供一键式部分重新配置设计流程,能动态重新配置 FPGA 部分区域,其余区域正常运行。同时优化了 Stratix 10 器件的重新配置时间,还支持传统和分层两种重新配置流程,助力加速产品上市。 * 快速重新编译适配:针对 Stratix 10 器件支持快速重新编译功能,对 Signal Tap 逻辑分析仪提供完善支持,包含适配后增量布线支持,小幅度修改 HDL 代码后无需完整重编,减少重复工作量。 * Quartus Prime

By Ne0inhk
法奥机器人ROS2环境搭建

法奥机器人ROS2环境搭建

目录 第一章  SDK文件准备    1.1  机器人软件版本查看  第二章 测试平台搭建 2.1虚拟平台安装                     2.1.1虚拟机安装              2.1.2ubuntu 的安装 第三章 软件环境搭建         3.1  vscode安装 3.2vscode插件安装 3.3  ROS 及环境变量配置 3.1.1 Ros2-humble版本 安装 3.1.2  Ros-control版本安装 3.1.3   Moveit2版本安装 第四章  插件包导入及插件测试         4.1 MOVEIT2插件包导入 4.2 RVIZ 仿真操作简介

By Ne0inhk
【基于Tang Nano 9K FPGA 实现BCD-数码管译码器】点亮数码管~

【基于Tang Nano 9K FPGA 实现BCD-数码管译码器】点亮数码管~

目录 * 一. 项目背景&目标 * 二. 硬件说明 * 1.Tang Nano 9K FPGA 开发板(GW1NR-9 系列器件) * 2.数码管 SR420561K * 3.IO 电气约束(.cst 的意义) * 三. BCD七段译码器(display8x1) * 1.BCD码 * 2.七段(a~g)+ dp 译码 * 四. 四位数码管位选模块(display8x4) * 1.位选信号约定 * 五.顶层模块设计(display) * 1.系统框图 * 2.实物图 一. 项目背景&目标

By Ne0inhk

零成本搭建飞书机器人:手把手教你用Webhook实现高效消息推送

1. 为什么你需要一个飞书机器人? 在日常工作中,我们经常需要处理各种通知需求。比如系统报警、任务提醒、审批结果通知等等。传统的解决方案包括短信、邮件或者第三方推送平台,但这些方式要么成本高,要么实时性差。飞书机器人提供了一种零成本、高效率的替代方案。 我去年负责的一个ERP系统升级项目就遇到了这个问题。当时我们需要在关键业务流程节点给不同部门的同事发送实时通知。如果使用短信,按照每天200条计算,一个月就要花费上千元。后来我们改用飞书机器人,不仅完全免费,还能实现更丰富的消息格式和精准的@提醒功能。 飞书机器人本质上是一个自动化程序,它通过Webhook技术接收外部系统的消息,并转发到指定的飞书群聊中。这种机制特别适合企业内部系统与飞书之间的集成,比如: * 运维报警通知 * 审批流程提醒 * 业务系统状态更新 * 日报/周报自动推送 * 数据监控预警 2. 5分钟快速创建你的第一个机器人 创建飞书机器人非常简单,不需要任何开发经验。下面我以电脑端操作为例,手把手带你完成整个过程。 首先打开飞书客户端,进入你想要添加机器人的群聊。点击右上角的"..."菜单,

By Ne0inhk