首页 > 其他分享 >“RAII资源获取就是初始化”的好处

“RAII资源获取就是初始化”的好处

时间:2024-09-23 16:47:14浏览次数:6  
标签:文件 初始化 RAII fclose filename 获取 关闭 句柄 fh

RAII指的是“资源获取就是初始化”(Resource Allocation Is Initialization),它被视作C++中最强大的编程范式之一
简单说来,它指的是,用构造函数来获取一个对象的资源,相应的,借助析构函数来释放对象的资源

为了理解这一范式的用处,让我们考虑某个函数使用文件句柄时的情况:

void doSomethingWithAFile(const char* filename)
{
    // 首先,让我们假设一切都会顺利进行。

    FILE* fh = fopen(filename, "r"); // 以只读模式打开文件

    doSomethingWithTheFile(fh);
    doSomethingElseWithIt(fh);

    fclose(fh); // 关闭文件句柄
}

不幸的是,随着错误处理机制的引入,事情会变得复杂。
假设fopen函数有可能执行失败,而doSomethingWithTheFiledoSomethingElseWithIt会在失败时返回错误代码。
(虽然异常是C++中处理错误的推荐方式,但是某些程序员,尤其是有C语言背景的,并不认可异常捕获机制的作用)。
现在,我们必须检查每个函数调用是否成功执行,并在问题发生的时候关闭文件句柄

bool doSomethingWithAFile(const char* filename)
{
    FILE* fh = fopen(filename, "r"); // 以只读模式打开文件
    if (fh == nullptr) // 当执行失败是,返回的指针是nullptr
        return false; // 向调用者汇报错误

    // 假设每个函数会在执行失败时返回false
    if (!doSomethingWithTheFile(fh)) {
        fclose(fh); // 关闭文件句柄,避免造成内存泄漏。
        return false; // 反馈错误
    }
    if (!doSomethingElseWithIt(fh)) {
        fclose(fh); // 关闭文件句柄
        return false; // 反馈错误
    }

    fclose(fh); // 关闭文件句柄
    return true; // 指示函数已成功执行
}

C语言的程序员通常会借助goto语句简化上面的代码:

bool doSomethingWithAFile(const char* filename)
{
    FILE* fh = fopen(filename, "r");
    if (fh == nullptr)
        return false;

    if (!doSomethingWithTheFile(fh))
        goto failure;

    if (!doSomethingElseWithIt(fh))
        goto failure;

    fclose(fh); // 关闭文件
    return true; // 执行成功

failure:
    fclose(fh);
    return false; // 反馈错误
}

如果用异常捕获机制来指示错误的话,代码会变得清晰一些,但是仍然有优化的余地。

void doSomethingWithAFile(const char* filename)
{
    FILE* fh = fopen(filename, "r"); // 以只读模式打开文件
    if (fh == nullptr)
        throw std::exception("Could not open the file.");

    try {
        doSomethingWithTheFile(fh);
        doSomethingElseWithIt(fh);
    }
    catch (...) {
        fclose(fh); // 保证出错的时候文件被正确关闭
        throw; // 之后,重新抛出这个异常
    }

    fclose(fh); // 关闭文件
    // 所有工作顺利完成
}

相比之下,使用C++中的文件流类(fstream)时,fstream会利用自己的析构器来关闭文件句柄。只要离开了某一对象的定义域,它的析构函数就会被自动调用

void doSomethingWithAFile(const std::string& filename)
{
    // ifstream是输入文件流(input file stream)的简称
    std::ifstream fh(filename); // 打开一个文件

    // 对文件进行一些操作
    doSomethingWithTheFile(fh);
    doSomethingElseWithIt(fh);

} // 文件已经被析构器自动关闭

与上面几种方式相比,这种方式有着明显的优势:

  1. 无论发生了什么情况,资源(此例当中是文件句柄)都会被正确关闭。
    只要你正确使用了析构器,就不会因为忘记关闭句柄,造成资源的泄漏。
  2. 可以注意到,通过这种方式写出来的代码十分简洁。
    析构器会在后台关闭文件句柄,不再需要你来操心这些琐事。
  3. 这种方式的代码具有异常安全性。
    无论在函数中的何处拋出异常,都不会阻碍对文件资源的释放。

地道的C++代码应当把RAII的使用扩展到各种类型的资源上,包括:

  • 用unique_ptr和shared_ptr管理的内存
  • 各种数据容器,例如标准库中的链表、向量(容量自动扩展的数组)、散列表等;
    当它们脱离作用域时,析构器会自动释放其中储存的内容。
  • 用lock_guard和unique_lock实现的互斥

标签:文件,初始化,RAII,fclose,filename,获取,关闭,句柄,fh
From: https://www.cnblogs.com/yubo-guan/p/18427306

相关文章

  • 从url中获取文件名
    比如https://abc.com/files/xx.zip,或许xx//文件名转为小驼峰exportconstkebabCase_to_camelCase=(fileName)=>{//转换为小写,并用正则表达式替换每个分隔符后的字符为大写(除非它是字符串的第一个字符)constnewfileName=fileName.toLowerCase()//先转换为......
  • SpringBoot 初始化资源
    1、使用接口ApplicationRunner和CommandLineRunner这两个接口都是在容器运行后执行的,如下图示 如果项目需要在系统启动时,初始化资源,可以继承这两个接口,实现诸如缓存预热、DB连接等。实现ApplicationRunner接口@ComponentpublicclassMyApplicationRunnerimplementsApp......
  • C++类成员变量初始化顺序
    C++类成员变量初始化顺序类成员初始化顺序与其在类中声明顺序一致。比如classDemo{public: Demo(intd) :_d1{d},_d2{_d1+10} { } voidshow(){ std::cout<<"d1="<<_d1<<std::endl; std::cout<<"d2="<<_d2<<std:......
  • dayjs 获取本周、上一周、下一周的第一天和最后一天的日期(周一&周日)
    npmidayjs--saveimportdayjsfrom'dayjs';/***获取日期的函数*/updateWeek(weekOffset){consttoday=dayjs()consttimestamp=Math.round(newDate())constdate=newDate(timestamp)constweekday=date.getDay()......
  • python获取kafka队列长度
    #pipinstallkafka-pythonfromkafkaimportKafkaConsumerfromkafka.structsimportTopicPartitiondefget_queue_length(topic_list,kafka_host,kafka_group):partition_list=[TopicPartition(it[1],0)foritintopic_list]consumer=KafkaCons......
  • Js中获取鼠标中的某一个点的位置以及getBoundingClientRect
    getBoundingClientRect() 是一个用于获取元素位置和尺寸信息的方法。它返回一个DOMRect对象,其提供了元素的大小及其相对于视口的位置,其中包含了以下属性: x:元素左边界相对于视口的x坐标。y:元素上边界相对于视口的y坐标。width:元素的宽度。height:元素的高度。top:元素......
  • 场景初始化
    获取初始化的元素//常规consttargetdom = document.getElementById('targetdom')//vue3consttargetdom = ref('targetdom')//reactconsttargetdom = ref('targetdom')初始化相机、场景、光源、renderconstcamera=newThree.PerspectiveCamera(......
  • C++ 列表初始化 {}
    花括号的形式{},进行列表初始化,在C++11中初始化变量到了全面的应用。可参看《C++Primer》P39P76P88等相关内容信息。Note:当我们提供一个类内初始值时,必须以符号=或者花括号表示。《C++Primer》P246。如下:classDog{public:Dog(intage):m_age(age){}......
  • Ubuntu24.04安装及初始化配置
    一、系统简介Ubuntu基于debian以桌面应用为主的开源操作系统,长期支持版本LTS通常每两年发布一次,如最新的版本24.04,上一个版本22.04,每个版本有5年支持周期,适合企业生产服务器使用,另外对容器支持较好,很多docker基础镜像采用utuntu。国产替代操作系统基于debian的有银河麒麟V10、统信U......
  • Spring Boot利用dag加速Spring beans初始化
    1.什么是Dag?有向无环图(DirectedAcyclicGraph),简称DAG,是一种有向图,其中没有从节点出发经过若干条边后再回到该节点的路径。换句话说,DAG中不存在环路。这种数据结构常用于表示并解决具有依赖关系的问题。DAG的特性首先,DAG中的节点可以有入度和出度。节点的入度是指指向该......