C++惯用法之RAII思想: 资源管理

news2024/12/23 12:28:48

C++编程技巧专栏:http://t.csdnimg.cn/eolY7

目录

1.概述

 2.RAII的应用

2.1.智能指针

2.2.文件句柄管理

2.3.互斥锁

3.注意事项

3.1.禁止复制

3.2.对底层资源使用引用计数法

3.3.复制底部资源(深拷贝)或者转移资源管理权(移动语义)

4.RAII的优势和挑战

5.总结


1.概述

        RAII是Resource Acquisition Is Initialization的缩写,即“资源获取即初始化”。RAII原则的基本思想是将资源的生命周期与对象的生命周期绑定在一起。它是C++语言的一种管理资源、避免资源泄漏的惯用法,利用栈的特点来实现,这一概念最早由Bjarne Stroustrup提出。在函数中由栈管理的临时对象,在函数结束时会自动析构,从而自动释放资源,因此,我们可以通过构造函数获取资源,通过析构函数释放资源。这种自动管理资源的方式可以大大减少资源泄漏、野指针和其他与资源管理相关的问题。常见的写法为:

Object() {
    // acquire resource in constructor
}
~Object() {
    // release resource in destructor
}

 2.RAII的应用

2.1.智能指针

智能指针是RAII原则在内存管理中的一个典型应用。C++11引入了多种智能指针类型,如std::unique_ptr、std::shared_ptr和std::weak_ptr,它们可以自动管理动态分配的内存。

例如,使用std::unique_ptr可以确保在不需要动态分配的内存时自动释放它:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass created\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

int main() {
    {
        std::unique_ptr<MyClass> ptr(new MyClass()); // MyClass对象被创建
        // 当ptr离开这个作用域时,它会自动释放所指向的MyClass对象
    } // MyClass对象在这里被销毁,输出"MyClass destroyed"
    return 0;
}

在这个例子中,当ptr离开其作用域时,std::unique_ptr的析构函数会被调用,从而释放它所指向的MyClass对象。这种自动的内存管理方式避免了手动调用delete可能导致的错误。

2.2.文件句柄管理

另一个常见的应用是使用RAII原则管理文件句柄。通过创建一个封装了文件句柄的类,可以确保在不需要文件时自动关闭它。

例如:

#include <fstream>
#include <iostream>

class FileWrapper {
public:
    FileWrapper(const std::string& filename, std::ios_base::openmode mode)
        : file_(filename, mode) {
        if (!file_.is_open()) {
            throw std::runtime_error("无法打开文件: " + filename);
        }
    }

    ~FileWrapper() {
        file_.close(); // 在析构函数中关闭文件句柄
    }

    // 提供对内部文件的访问(如果需要的话)
    std::fstream& file() { return file_; }

private:
    std::fstream file_; // 封装文件句柄的成员变量
};

在这个例子中,FileWrapper类的构造函数打开一个文件,并在析构函数中关闭它。这确保了即使在异常情况下,文件句柄也会被正确关闭。

2.3.互斥锁

在多线程编程中,std::lock_guard, std::unique_lock, std::shared_lock等也利用了RAII的原理,用于管理互斥锁。当这些类的等对象创建时,会自动获取互斥锁;当对象销毁时,会自动释放互斥锁。

std::lock_guard的构造函数如下:

template< class Mutex > class lock_guard;

std::lock_guard的析构函数会自动释放互斥锁,因此,我们可以通过std::lock_guard来管理互斥锁,从而避免忘记释放互斥锁。如:

std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx); // unlock when lock is out of scope

不使用RAII的情况下,我们需要手动释放互斥锁,如下所示:

std::mutex mtx;
mtx.lock();
// ...
mtx.unlock();

3.注意事项

在资源管理类中小心copy行为

  • 拷贝RAII对象必须考虑其管理的资源,针对其资源做出拷贝行为的实现
  • 常见的RAII对象拷贝行为:拒绝拷贝、引用计数法、深拷贝、资源所有权转移

并非所有资源都是基于堆的(heap-based),对于这种对象不能直接使用智能指针,需要自定义其资源管理类。例如:为了说明锁的资源管理行为,我们这里给定义一个锁,来替代C++里的锁

struct MyMutex {
    MyMutex() {
        printf("Construct MyMutex\n");
    }

    ~MyMutex() {
        printf("Deconstruct MyMutex\n");
    }
};

其上锁解锁行为:

void lock(MyMutex *) {
    printf("lock\n");
}

void unlock(MyMutex *) {
    printf("unlock\n");
}

锁的资源管理类,在构造函数获取资源(加锁),在析构函数释放资源(解锁):

struct Lock {
private:
    MyMutex *myMutex;
public:
    explicit Lock(MyMutex *mutex) : myMutex(mutex) {
        lock(myMutex);
    }

    ~Lock() {
        unlock(myMutex);
    }
};

使用:

int main() {
    MyMutex myMutex;
    {
        printf("---------\n");
        Lock lk(&myMutex);
        printf("---------\n");
        // 离开代码块将自动析构局部对象,因此会释放锁
    }
}
/*
Construct MyMutex
---------
lock
---------
unlock
Deconstruct MyMutex
*/

潜在风险,如果发生了拷贝行为:

Lock l1(&mutex);
Lock l2(l1);

那么将立即死锁(Linux里一般是非递归锁,重复加锁会造成死锁)

3.1.禁止复制

继承nocopyable,或者将拷贝相关函数设置为delete。如:

//[1]
class NonCopyable
{
protected:
    NonCopyable(const NonCopyable&){}
    NonCopyable& operator=(NonCopyable&){}
};

或

//[2]
class NonCopyable
{
public:
    NonCopyable(const NonCopyable&)=delete;
    NonCopyable& operator=(const NonCopyable&)=delete;
};

3.2.对底层资源使用引用计数法

思想:维护一个计数器,当最后一个使用者被销毁时,才真正释放资源,如:

struct Lock {
private:
    shared_ptr<MyMutex> mutexPtr;
public:
	// 将unlock函数设置为删除器
    explicit Lock(MyMutex *mutex) : mutexPtr(mutex, unlock) {
        lock(mutexPtr.get());
    }
    // 不必声明析构函数,因为mutexPtr是栈上对象,所以会被默认释放,那么智能指针就会调用其释放器unlock
};

3.3.复制底部资源(深拷贝)或者转移资源管理权(移动语义)

在资源管理类中提供对原始资源的访问

  • API常需要要求访问原始资源,所以RAII资源管理类应该提供访问原始资源的接口
  • 对原始资源可以由显示转换或者隐式转换获得.其在安全性和方便性上各有取舍

智能指针提供了get接口来访问原始资源

在其中要注意,不可以get一个智能指针去初始化另一个智能指针,否则会发生重复释放

int main() {
    shared_ptr<MyMutex> p1 = make_shared<MyMutex>();
    {
        shared_ptr<MyMutex> p2(p1.get());
        cout << p1.use_count() << " " << p2.use_count() << endl;
//        1 1
//        p2离开代码块,释放其管理的资源,p1指针指向被释放的内存
    }
}

程序将异常退出

4.RAII的优势和挑战

优势:

  1. 自动资源管理:通过绑定资源的生命周期与对象的生命周期,RAII自动处理资源的获取和释放,减少了手动管理的错误。

  2. 代码简洁性:RAII原则鼓励将资源管理逻辑封装在类中,使代码更加清晰和易于维护。

  3. 异常安全性:当使用RAII时,即使在异常情况下,资源也会被正确释放,这有助于提高程序的健壮性。

挑战:

  1. 资源所有权的转移:在使用RAII时,需要仔细考虑资源所有权的转移。例如,在使用智能指针时,需要明确何时使用std::move来转移所有权。

  2. 与旧代码的兼容性:在将RAII原则应用于现有代码库时,可能需要大量的重构工作来适应新的资源管理方式。

  3. 学习曲线:对于初学者来说,理解和正确应用RAII原则可能需要一些时间和经验。

5.总结

        RAII原则为C++程序员提供了一种强大且优雅的资源管理方法。通过将资源的生命周期与对象的生命周期绑定在一起,RAII不仅简化了资源管理,还提高了代码的健壮性和可维护性。然而,为了充分利用RAII的优势,程序员需要仔细设计类的接口和实现,并考虑到资源所有权和资源转移的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1489978.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XUbuntu22.04之如何找到.so库所在的软件包?(二百一十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Jupyter Notebook的安装和使用(windows环境)

一、jupyter notebook 安装 前提条件&#xff1a;安装python环境 安装python环境步骤&#xff1a; 1.下载官方python解释器 2.安装python 3.命令行窗口敲击命令pip install jupyter 4.安装jupyter之后&#xff0c;直接启动命令jupyter notebook,在默认浏览器中打开jupyte…

C/C++ 乘积尾零问题(蓝桥杯)

如下的10行数据&#xff0c;每行有10个整数&#xff0c;请你求出它们的乘积的末尾有多少个零&#xff1f; 5650&#xff0c;4542 3554 473 946 4114 3871 9073 90 4329 2758 7949 6113 5659 5245 7432 3051 4434 6704 3594 9937 1173 6866 3397 4759 7557 3070 2287 1453 9899…

stressapptest源码剖析:主函数main解析和sat类头文件分析

主函数main解析和sat类头文件分析 一、简介二、入口函数main.cc剖析三、SAT压力测试对象接口和数据结构总结 一、简介 stressapptest&#xff08;简称SAT&#xff09;是一种用于在Linux系统上测试系统稳定性和可靠性的工具&#xff0c;通过产生CPU、内存、磁盘等各种负载来测试…

web小游戏,蜘蛛纸牌

H5小游戏源码、JS开发网页小游戏开源源码大合集。无需运行环境,解压后浏览器直接打开。有需要的订阅后,私信本人,发源码,含60+小游戏源码。如五子棋、象棋、植物大战僵尸、贪吃蛇、飞机大战、坦克大战、开心消消乐、扑鱼达人、扫雷、打地鼠、斗地主等等。 <!DOCTYPE h…

智能驾驶规划控制理论学习06-基于优化的规划方法之数值优化基础

目录 一、优化概念 1、一般优化问题 2、全局最优和局部最优 二、无约束优化 1、无约束优化概述 2、梯度方法 通用框架 线性搜索 回溯搜索 3、梯度下降 基本思想 实现流程 ​4、牛顿法 基本思想 实现流程 5、高斯牛顿法 6、LM法&#xff08;Le…

甲类,乙类,甲乙类,D类功放

功率放大器&#xff1a; 简称功放,分为甲类&#xff0c;已类&#xff0c;甲乙类和D类。 首先要认识三极管&#xff0c;三极管最简单的理解为B极向E极流过一个较小的电流的时候&#xff0c;C极可以向E极流过一个较大的电流&#xff0c;而且两个电流之间呈现倍数关系&#xff0…

STM32启动过程及反汇编

STM32从Flash启动的过程&#xff0c;主要是从上电复位到main函数的过程&#xff0c;主要有以下步骤&#xff1a; 1.初始化堆栈指针 SP_initial_sp&#xff0c;初始化 PC 指针Reset_Handler 2.初始化中断向量表 3.配置系统时钟 4.调用 C 库函数_main 初始化用户堆栈&#xf…

Linux网络编程 ——UDP 通信

Linux网络编程 ——UDP 通信 1. UDP1.1 UDP 通信1.2 广播1.3 组播&#xff08;多播&#xff09; 2. 本地套接字 1. UDP 1.1 UDP 通信 输入 man 2 sendto 查看说明文档 #include <sys/types.h> #include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf…

内存安全的编程语言

美国政府新颁布《回归基础构件&#xff1a;通往安全软件之路》 《回归基础构件&#xff1a;通往安全软件之路》中&#xff0c;白宫国家网络主任办公室&#xff08;ONCD&#xff09;呼吁开发者使用「内存安全的编程语言」 内存安全的编程语言 根据NSA的建议&#xff0c;内存…

线程的同步互斥机制3月4日

题目&#xff1a; 代码&#xff1a; #include <stdio.h> #include <pthread.h> #include <string.h> #include <semaphore.h> #include <unistd.h>sem_t sem1,sem2;void* callback1(void*arg) {while(1){if(sem_wait(&sem1)<0) //等待…

keycloak-操作keycloak数据库添加用户及密码

一、环境描述 keycloak连接本地数据库的方法&#xff1a;keycloak-连接本地数据库-CSDN博客 连接数据库后&#xff0c; 用户数据表&#xff1a;user_entity 密码数据表&#xff1a;credential keycloak版本&#xff1a;23.0.7 二、开始干活 1、插入数据到用户表(user_entit…

使用 gulp-cleanwxss 清除小程序无用样式代码

小程序在迭代中&#xff0c;因没有及时清理无用样式&#xff0c;造成包体积越来越大。而通过人工判断清除工作量大&#xff0c;因而使用 gulp-cleanwxss 实现脚本清除。 一、Demo 演示 二、实现步骤 1、全局安装 gulp 命令行工具 yarn global add gulp-cli2、局部安装依赖 gu…

【Linux-shell系列】多脚本同时启动

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

机器学习笔记 大语言模型是如何运作的?一、语料库和N-gram模型

一、语料库 语言模型、ChatGPT和人工智能似乎无处不在。了解大型语言模型(LLM)“背后”发生的事情将是驾驭数字世界的关键。 首先在提示中键入一个单词,然后点击提交。您可以尝试新的提示,并根据需要多次重新生成响应。 这个我们称之为“T&C”的语言模型是在一…

Python使用模块和库编程

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 路在脚下&#xff0c;勇往直前&#x…

MongoDB入门教程

参考文档 https://blog.csdn.net/qq_26889387/article/details/116205819 https://blog.csdn.net/ncepu_Chen/article/details/98725104 1. 简介 ​ MongoDB 是由C语言编写的&#xff0c;是一个基于分布式文件存储的开源数据库系统。在高负载的情况下&#xff0c;添加更多的节…

PaddleOCR基于PPOCRv4的垂类场景模型微调——手写文字识别

PaddleOCR手写文字识别 一. 项目背景二. 环境配置三. 数据构造四. 模型微调五. 串联推理六. 注意事项七. 参考文献 光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;ORC是指对包含文本资料的图像文件进行分析识别处理&#xff0c;获取文字…

MB85RC铁电 FRAM驱动(全志平台linux)

测试几天发现一个bug&#xff0c;就是无法一次读取32个字节的数据&#xff0c;1-31,33,128,512都试过了&#xff0c;唯独无法读取32个字节&#xff0c;驱动未报错&#xff0c;但是读取的都是0&#xff0c;找不到原因&#xff0c;估计应该是全志iic驱动的问题&#xff0c;暂时没…

Golang Copy()方法学习

前言 主要是涉及到深浅拷贝相关的&#xff0c;但是在看的一个资料过程中发现他有错…并且一系列&#xff0c;复制粘贴他的&#xff0c;也都错了。 错误文章指路 很显然&#xff0c;Copy是深拷贝啊&#xff01;&#xff01;&#xff01; Copy功能 copy的代码很少&#xff0c…