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

45 C++智能指针的原理与模拟实现,内存泄漏与RAII

⭐上篇文章:44 C++智能指针详解:安全高效管理内存-CSDN博客

⭐代码仓库:橘子真甜 (yzc-YZC) - Gitee.com

目录

一. 使用类封装一个智能指针

二. 智能指针解决多次析构的方式

2.1 早期版本 auto_ptr

2.2 unique_ptr

2.3 shared_ptr⭐

2.4 weak_ptr

三. 内存泄漏与RAII

3.1 内存泄漏

3.2 RAII思想


一. 使用类封装一个智能指针

        智能指针是基于RAII思想构建的指针,需要在构造时候创建资源,析构的时候释放资源。这样才能保证不会有内存的泄漏。

        还需要使用模板保证能够存储不同类型的变量

        基本代码结构如下:在构造函数中初始化资源,析构函数中自动销毁资源。通过类的管理实现资源的开辟和销毁

#pragma oncetemplate <class T>
class mySmartPtr
{
public:mySmartPtr(const T *ptr): _ptr(ptr) {}~mySmartPtr(){if (_ptr != nullptr)delete _ptr;_ptr = nullptr}private:T *_ptr;
};

但是我们还不能使用,因为没有重载operator* 和 operator->

改进后的代码如下:

#pragma oncetemplate <class T>
class mySmartPtr
{
public:mySmartPtr(const T *ptr): _ptr(ptr) {}~mySmartPtr(){if (_ptr != nullptr)delete _ptr;_ptr = nullptr;}T& operator*(){return *_ptr;}T *operator->(){return _ptr;}private:T *_ptr;
};

重新测试主程序,运行结果如下。

        此时仍然有问题,假如有两个智能指针管理同一个对象。这样同一块资源就会被析构两次,这样就会非法访问内存。

二. 智能指针解决多次析构的方式

2.1 早期版本 auto_ptr

        上篇文章提到:auto_ptr是通过转移管理权来防止多次析构的,不过现在已经被弃用了。因为有更好的方式,auto_ptr转移管理权会容易导致原指针变为野指针

        不过auto_ptr实现的思路还是可以学习的!转移管理权首先要考虑转移的指针是否为空指针

拷贝构造:直接让转移的指针指向我管理的空间即可,然后让自己置空。

调用 p1 = p2 即赋值的时候:则需要释放转移指针的资源并置空,然后执行上面操作。

是不是有点抽象?可以看下面的图画

代码如下:

template <class T>
class myAutoPtr
{
public:myAutoPtr(T *ptr): _ptr(ptr) {}~mySharedPtr(){//直接调用release释放自己即可release();}// 拷贝构造函数myAutoPtr(myAutoPtr<T> &aptr): _ptr(aptr._ptr){aptr._ptr = nullptr;}// operator重载myAutoPtr<T> &operator=(myAutoPtr<T> &aptr){if (this != aptr){if(aptr != nullptr){delete _ptr;_ptr = aptr._ptr;aptr._ptr = nullptr;}}return *this;}T &operator*(){return *_ptr;}T *operator->(){return _ptr;}private:T *_ptr;
};

2.2 unique_ptr

        unique_ptr通过防止拷贝来保证只有一个智能指针对象管理一个对象资源,这样就不会造成多次析构的问题了!解决方法简单粗暴,但是好用。

代码如下:

template <class T>
class myUniquePtr
{
public:myUniquePtr(T *ptr): _ptr(ptr) {}~myUniquePtr(){if (_ptr != nullptr)delete _ptr;_ptr = nullptr;}// 禁用拷贝构造和赋值重载myUniquePtr(myUniquePtr<T> &mup) = delete;myUniquePtr<T> &operator=(myUniquePtr<T> &mup) = delete;T &operator*(){return *_ptr;}T *operator->(){return _ptr;}private:T *_ptr;
};

2.3 shared_ptr⭐

        共享指针通过引用计数这个变量来保证多次析构的问题,每有一个指针指向当前对象,引用计数就++。当引用计数为0,即没有指针指向这个对象的时候就销毁资源。

        不过这样也有了新的问题!如何保证引用计数的线程安全问题?

        C++11内部是通过原子变量的方式来保证的!原子变量作为引用计数,原子变量++ --操作都是线程安全的

        这里使用mutex互斥锁的方式来实现保护shared_ptr中的引用计数

        如果使用mutex保护引用计数,需要如何定义count呢?

        int count 不能这样,因为每一个指针都有自己的count。static int count也不行,因为这样会导致不同的类型有相同的count。int & 也不行,因为管理比较复杂。

        使用 int *在堆中创建资源是最好管理的!

代码如下:

部分函数:

    //增加引用计数void add_ref_count(){//注意加锁std::unique_lock<std::mutex> ul(_lock);(*_count)++;}
    // 释放自己的资源,引用计数--,为0释放所有资源,不为0,啥都不做void release(){bool flag = false;{std::unique_lock<std::mutex> ul(_lock);if (--(*_count) == 0){// 说明引用计数为0,需要释放指向的对象if (_ptr != nullptr)delete _ptr;_ptr = nullptr;// 同时需要释放引用计数delete _count;_count = nullptr;// 处于加锁状态,不可释放锁的资源// 通过标志位来释放锁资源flag = true;}}// 释放锁资源if (flag)delete _lock;}

类代码如下:

注意:拷贝和赋值的时候需要将所有成员变量都拷贝

release时候需要注意_ptr是否为空,如果为空就不需要释放数据

如果赋值的指针有着自己管理的资源,需要先释放自己资源然后再管理新资源

template <class T>
class mySharedPtr
{
public:mySharedPtr(T *ptr): _ptr(ptr), _count(new int), _lock(new std::mutex) {}~mySharedPtr(){release();}mySharedPtr(mySharedPtr<T> &mup): _ptr(mup._ptr), _count(mup._count),_lock(mup._lock) // 初始化ptr和count保证二者一致{// 增加引用计数add_ref_count();}mySharedPtr<T> &operator=(mySharedPtr<T> &mup){if (this != &mup){// 如果自己有管理的对象的话,需要释放自己的资源release();// 更新所有的资源_ptr = mup._ptr;_count = mup._count;_lock = mup._lock;// 增加引用计数add_ref_count();}return *this;}T &operator*(){return *_ptr;}T *operator->(){return _ptr;}private:// 增加引用计数void add_ref_count(){// 注意加锁std::unique_lock<std::mutex> ul(*_lock);(*_count)++;}// 释放自己的资源,引用计数--,为0释放所有资源,不为0,啥都不做void release(){bool flag = false;{std::unique_lock<std::mutex> ul(*_lock);if (--(*_count) == 0 && _ptr != nullptr){// 说明引用计数为0,需要释放指向的对象if (_ptr != nullptr)delete _ptr;_ptr = nullptr;// 同时需要释放引用计数delete _count;_count = nullptr;// 处于加锁状态,不可释放锁的资源// 通过标志位来释放锁资源flag = true;}}// 释放锁资源if (flag)delete _lock;}private:T *_ptr;int *_count;       // 共享计数std::mutex *_lock; // 互斥锁
};

测试代码与运行结果

#include <iostream>
#include "smartptr.hpp"int main()
{int *a = new int(2);mySharedPtr<int> ptr1(a);mySharedPtr<int> ptr2(ptr1);mySharedPtr<int> ptr3(new int(8));ptr3 = ptr1;std::cout << "*ptr1:" << *ptr1 << std::endl;std::cout << "*ptr2:" << *ptr2 << std::endl;std::cout << "*ptr3:" << *ptr3 << std::endl;*ptr1 = 12345;std::cout << "*ptr1:" << *ptr1 << std::endl;std::cout << "*ptr2:" << *ptr2 << std::endl;std::cout << "*ptr3:" << *ptr3 << std::endl;
}

可以看到,这些指针管理同一个对象,并且没有

2.4 weak_ptr

        weak_ptr是用于解决shared_ptr循环引用的问题。在上篇文章中已经详细说明,这里就不过多介绍和实现了。

三. 内存泄漏与RAII

3.1 内存泄漏

        内存泄漏是由于某进程由于代码失误或者其他错误操作造成的。比如 动态开辟了资源,使用完毕了却不释放,一直占用内存却不释放。又比如,父子进程中,子进程退出后,父进程不去回收子进程的资源,这样也会造成内存泄漏等。

        这些占用内存又没有使用资源是没有用的,如果一个系统长时间运行,并且有导致内存泄漏的代码。最后整个系统会由于内存泄漏卡死。

        解决内存泄漏需要从三个方面来说:

1 规范写代码,申请资源需要释放

2 使用如智能指针这样的技术来保证无用的资源被释放

3 代码运行后可以使用一些工具如 valgrind

3.2 RAII思想

RAII思想是:资源获取即初始化,资源可以自动销毁。用于减少内存泄漏

比如智能指针就是 在构造函数中完成资源初始化,当使用完毕后通过析构函数自动销毁

又比如 lock_guard unique_guard等锁,初始化时候进行加锁,解锁之后进行销毁

http://www.dtcms.com/a/568746.html

相关文章:

  • 时序数据库系列(二):InfluxDB安装配置从零搭建
  • Rust实战开发之图形界面开发入门(egui crate)
  • 如何在centos 中运行arm64程序
  • 工业时序数据库TDengine 架构、性能与实战全解析
  • 朗迪锋@2025人因工程与智能系统交互国际会议
  • django初识与安装
  • 哪个网站做译员好设计页面跳转
  • 嘉兴网站制作费用手机html5网站开发
  • <P2016 战略游戏>
  • OpenCV环境配置(QT 6.6.1 MSVC2019 64bit + OpenCV – 4.12.0)
  • 用zookpeer搭建Hadoop的HA集群,组件启动的启动顺序是什么?
  • 中国石油西北化工聚丙烯产品首次出口非洲
  • Node.js 自动替换脚本工具:一键完成多项目批量修改与资源替换
  • 【智能手表篇】基于Handdle AI的台式电脑一体机Housing外观缺陷检测方案
  • 域名申请了怎么做网站高级搜索百度
  • 北京 网站策划公司c2c模式类型
  • 【零基础学MySQL】第十五章:分库分表
  • Android Gradle 的 compileOptions 与 Kotlin jvmTarget 全面理解(含案例)
  • K8s Pod生命周期完全指南
  • 生成式搜索不识你?用GEO重建识别路径
  • CSP-J教程——第一阶段——第四课:算术与逻辑运算
  • k8s——pod控制器详解
  • k8s --- resource: Pod, ReplicaSet and Deployment
  • App 上架需要什么?从开发者账号到开心上架(Appuploader)免 Mac 上传的完整流程指南
  • 高端定制网站网站建设建设多少钱
  • 写SCI论文需要用到的工具这了
  • WebFlux 执行流程与背压机制剖析
  • wordpress4.9+多站点WordPress购物按钮
  • 深入解析Kubernetes中的Ephemeral Containers:故障诊断的“急救针”
  • 安卓二次打包技术深度拆解:从逆向篡改到防护逻辑