`
wan_2004
  • 浏览: 140428 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

RAII - C++ 资源管理方式

    博客分类:
  • C++
阅读更多

        RAII 是指 C++ 语言中资源管理的一种方式,简单而又不会出什么岔子,英文全拼也说明了他的用法, 它是“ R esource A cquisition I s I nitialization ”的首字母缩写

 

        首先让我们来明确资源的概念,在计算机系统中,资源是数量有限且对系统正常运转具有一定作用的元素。比如,内存,文件句柄,网络套接字( network sockets ),互斥锁( mutex locks )等等,它们都属于系统资源。由于资源的数量不是无限的,有的资源甚至在整个系统中仅有一份,因此我们在使用资源时必须严格遵循的步骤是:

1.         获取资源

2.         使用资源

3.         释放资源

例如在下面的 UseFile 函数中:

void UseFile(char const* fn)
{
    FILE* f = fopen(fn, "r");        // 获取资源
    // 在此处使用文件句柄f...代码          // 使用资源
    fclose(f);                       // 释放资源
}
 

        调用 fopen() 打开文件就是获取文件句柄资源,操作完成之后,调用 fclose() 关闭文件就是释放该资源。资源的释放工作至关重要,如果只获取而不释放,那么资源最终会被耗尽。上面的代码是否能够保证在任何情况下都调用 fclose 函数呢?请考虑如下情况:

void UseFile(char const* fn)
{
    FILE* f = fopen(fn, "r");        // 获取资源
    // ........... 使用资源代码
    if (!g()) return;                // 如果操作g失败!
    // ...
    if (!h()) return;                // 如果操作h失败!
    // ...
    fclose(f);                       // 释放资源
}
 

 

但是在使用文件 f 的过程中,因某些操作失败而造成函数提前返回的现象经常出现。这时函数 UseFile 的执行流程将变为:

        很明显,这里忘记了一个重要的步骤:在操作 g h 失败之后, UseFile 函数必须首先调用 fclose() 关闭文件,然后才能返回其调用者,否则会造成资源泄漏。因此,需要将 UseFile 函数修改为:

void UseFile(char const* fn)
{
    FILE* f = fopen(fn, "r");        // 获取资源
    // 使用资源
    if (!g()) { fclose(f); return; }
    // ...
    if (!h()) { fclose(f); return; }
    // ...
    fclose(f);                       // 释放资源
}
 

        现在的问题是:用于释放资源的代码 fclose(f) 需要在不同的位置重复书写多次。如果再加入异常处理,情况会变得更加复杂。例如,在文件 f 的使用过程中,程序可能会抛出异常:

void UseFile(char const* fn)
{
    FILE* f = fopen(fn, "r");        // 获取资源
    // 使用资源
    try {
        if (!g()) { fclose(f); return; }
        // ...
        if (!h()) { fclose(f); return; }
        // ...
    }
    catch (...) {
        fclose(f);                   // 释放资源
        throw;
    }
    fclose(f);                       // 释放资源
}
 

        我们必须依靠 catch(...) 来捕获所有的异常,关闭文件 f , 并重新抛出该异常。随着控制流程复杂度的增加,需要添加资源释放代码的位置会越来越多。如果资源的数量还不止一个,那么程序员就更加难于招架了。可以想象 这种做法的后果是:代码臃肿,效率下降,更重要的是,程序的可理解性和可维护性明显降低。是否存在一种方法可以实现资源管理的自动化呢?答案是肯定的。假 设 UseResources 函数要用到 n 个资源,则进行资源管理的一般模式为:

void UseResources()  // 注意不要使用指针(智能指针可以)
{
    // 获取资源1
    // ...
    // 获取资源n
   
    // 使用这些资源
   
    // 释放资源n
    // ...
    // 释放资源1
}
 

       不难看出资源管理技术的关键在于:要保证资源的释放顺序与获取顺序严格相反。这自然使我们联想到局部对象的创建和销毁过程。在 C++ 中,定义在栈空间上的局部对象称为自动存储( automatic memory )对象。管理局部对象的任务非常简单,因为它们的创建和销毁工作是由系统自动完成的。我们只需在某个作用域( scope )中定义局部对象(这时系统自动调用构造函数以创建对象),然后就可以放心大胆地使用之,而不必担心有关善后工作;当控制流程超出这个作用域的范围时,系统会自动调用析构函数,从而销毁该对象。

      如果系统中的资源也具有如同局部对象一样的特性,自动获取,自动释放,那该有多么美妙啊!。事实上,您的想法已经与 RAII 不谋而合了。既然类是 C++ 中的主要抽象工具,那么就将资源抽象为类,用局部对象来表示资源,把管理资源的任务转化为管理局部对象的任务。这就是 RAII 惯用法的真谛!可以毫不夸张地说, RAII 有效地实现了 C++ 资源管理的自动化。例如,我们可以将文件句柄 FILE 抽象为 FileHandle 类:

class FileHandle {
public:
    FileHandle(char const* n, char const* a) { p = fopen(n, a); }
    ~FileHandle() { fclose(p); }
private: 
    FileHandle(FileHandle const&);
    FileHandle& operator= (FileHandle const&); // 禁止拷贝操作
    FILE *p;
};
 

        FileHandle 类的构造函数调用 fopen() 获取资源; FileHandle 类的析构函数调用 fclose() 释放资源。请注意,考虑到 FileHandle 对象代表一种资源,它并不具有拷贝语义,因此我们将拷贝构造函数和赋值运算符声明为私有成员。如果利用 FileHandle 类的局部对象表示文件句柄资源,那么前面的 UseFile 函数便可简化为:

void UseFile(char const* fn)
{
    FileHandle file(fn, "r");
    // 在此处使用文件句柄f...
    // 超出此作用域时,系统会自动调用file的析构函数,从而释放资源

}

        现在我们就不必担心隐藏在代码之中的 return 语句了;不管函数是正常结束,还是提前返回,系统都必须“乖乖地”调用 f 的析构函数,资源一定能被释放。

且慢!如若使用文件 file 的代码中有异常抛出,难道析构函数还会被调用吗?此时 RAII 还能如此奏效吗?问得好。事实上,当一个异常抛出之后,系统沿着函数调用栈,向上寻找 catch 子句的过程,称为栈辗转开解( stack unwinding )。 C++ 标准规定,在辗转开解函数调用栈的过程中,系统必须确保调用所有已创建起来的局部对象的析构函数。例如:

void Foo()
{
    FileHandle file1("n1.txt", "r");
    FileHandle file2("n2.txt", "w");
    Bar();       // 可能抛出异常
    FileHandle file3("n3.txt", "rw")
}
 

        当 Foo() 调用 Bar() 时,局部对象 file1 file2 已经在 Foo 的函数调用栈中创建完毕,而 file3 却尚未创建。如果 Bar() 抛出异常,那么 file2 file1 的析构函数会被先后调用(注意:析构函数的调用顺序与构造函数相反);由于此时栈中尚不存在 file3 对象,因此它的析构函数不会被调用。只有当一个对象的构造函数执行完毕之后,我们才认为该对象的创建工作已经完成。栈辗转开解过程仅调用那些业已创建的对象的析构函数。

 

        RAII 惯用法同样适用于需要管理多个资源的复杂对象。例如, Widget 类的构造函数要获取两个资源:文件 myFile 和互斥锁 myLock 。每个资源的获取都有可能失败并且抛出异常。为了正常使用 Widget 对象,这里我们必须维护一个不变式( invariant ):当调用构造函数时,要么两个资源全都获得,对象创建成功;要么两个资源都没得到,对象创建失败。获取了文件而没有得到互斥锁的情况永远不能出现,也就是说,不允许建立 Widget 对象的“半成品”。如果将 RAII 惯用法应用于成员对象,那么我们就可以实现这个不变式:

class Widget {
public:
    Widget(char const* myFile, char const* myLock)
    : file_(myFile),     // 获取文件myFile
      lock_(myLock)      // 获取互斥锁myLock
    {}
    // ...
private:
    FileHandle file_;
    LockHandle lock_;
};

        FileHandle LockHandle 类的对象作为 Widget 类的数据成员,分别表示需要获取的文件和互斥锁。资源的获取过程就是两个成员对象的初始化过程。在此系统会自动地为我们进行资源管理,程序员不必显式地添加任何异常处理代码。

         RAII 的本质 内容是用对象代表资源,把管理资源的任务转化为管理对象的任务,将资源的获取和释放与对象的构造和析构对应起来,从而确保在对象的生存期内资源始终有效,对象销毁时资源必被释放。换句话说,拥有对象就等于拥有资源,对象存在则资源必定存在。 由此可见, RAII 惯用法是进行资源管理的有力武器。 C++ 程序员 依靠 RAII 写出的代码不仅简洁优雅,而且做到了异常安全。

 

本文截取自:http://www.cnblogs.com/hsinwang/articles/214663.html

分享到:
评论

相关推荐

    DEV-C++IDE(DEV的C++开发环境)

    1. **全面支持C++标准**:DEV-C++遵循最新的C++编程标准,允许开发者利用现代C++特性如模板、STL(标准模板库)、RAII(资源获取即初始化)等进行编程。 2. **友好的中文界面**:对于中文用户,DEV-C++提供了本地化...

    C++ 中的 RAII(资源获取即初始化)是什么

    RAII是C++中一种强大的资源管理技术,它通过将资源管理封装在对象的生命周期中,简化了资源管理,提高了代码的安全性和可维护性。通过使用RAII,开发者可以减少因资源管理不当导致的内存泄漏和其他问题,从而编写出...

    C语言复习1-C++.zip

    标题 "C语言复习1-C++.zip" 暗示了这是一个关于C语言学习资料的压缩包,其中可能包含了与C语言基础、进阶概念以及与C++相关的学习资源。C语言是计算机科学的基础,而C++是C语言的增强版,引入了面向对象编程的概念。...

    C++小知识.rar----C++

    - **RAII(Resource Acquisition Is Initialization)**:理解RAII原则,以确保资源在生命周期中的正确管理。 - **C++11及更高版本的新特性**:学习并运用C++11及后续版本引入的新功能,如lambda表达式、右值引用...

    进销存管理系统-C++.rar

    RAII则可以帮助管理资源,避免内存泄漏。 此外,为了提高用户交互体验,通常会结合图形用户界面(GUI)库,如Qt或wxWidgets,来构建系统前端。这需要理解事件驱动编程模型,以及如何在C++中集成GUI库。 在实际开发...

    一本学习C++很不错的书-----C++编程思想

    6. **智能指针**:C++11引入的智能指针(如shared_ptr、unique_ptr等)解决了原始指针可能导致的内存管理问题,使得资源管理更加安全。 7. **Lambda表达式**:C++11引入的lambda表达式简化了函数对象的定义,使得在...

    高质量C-C++编程指南

    7. **异常安全编程**:掌握异常传播和资源管理策略,如 RAII(Resource Acquisition Is Initialization)原则。 8. **多线程编程**:了解 C++11 及以后版本的并发和线程库,实现高效的多线程应用。 9. **C++ 标准库*...

    高质量程序设计指南--C++/C语言(第3版)]-[林锐.pdf

    4. **资源管理**:掌握智能指针(C++)和RAII(资源获取即初始化,C++特有)来自动管理资源,避免资源泄露。 ### 代码优化与调试 1. **性能优化**:理解编译器优化选项,学会使用性能分析工具来分析代码瓶颈,并...

    编程艺术-c++

    8. **RAII(Resource Acquisition Is Initialization)**:C++鼓励使用RAII技术,即将资源的生命周期绑定到对象的生命周期,确保资源的正确管理。 9. **C++11及以后的更新**:C++11、C++14、C++17和C++20等新标准...

    学习心得--C++学习经典

    15. **资源管理与优化**:了解并应用资源管理技术(如智能指针、RAII等),可以有效地控制内存使用,避免资源泄露,提高程序性能。 16. **跨平台编程**:熟悉C++的跨平台特性,能够使得所编写的程序在不同的操作...

    挑战30天C-C++入门极限系列教程

    - **智能指针**:学习unique_ptr、shared_ptr和weak_ptr,理解内存管理和RAII(Resource Acquisition Is Initialization)原则。 - **Lambda表达式**:了解并使用C++11引入的Lambda函数,提升代码可读性和简洁性。...

    VS2019-C++-trace.rar|trace.rar

    在C++编程中,内存管理是一项关键任务,尤其是在大型项目中。内存泄漏是常见的编程错误,它发生在程序分配了内存但未能正确释放时。Visual Studio 2019(VS2019)提供了一些强大的工具来帮助开发者检测和解决这类...

    Memory-and-C++-debugging-at-EA-2015.zip

    2. **智能指针**:如`std::unique_ptr`, `std::shared_ptr`和`std::weak_ptr`,这些智能指针在C++11及更高版本中引入,用于自动管理内存,防止资源泄露。 3. **内存对齐**:讨论了内存对齐的概念,它影响数据结构的...

    C-C++使用技巧1001例

    14. **RAII(Resource Acquisition Is Initialization)**:C++的资源管理策略,通过对象生命周期管理资源,避免资源泄露。 15. **C++11及以后的更新**:包括lambda表达式、右值引用、智能指针等新特性,使得C++...

    浙江大学-翁凯-C++课件+源代码(12)

    4. **智能指针与RAII(Resource Acquisition Is Initialization)**:智能指针是RAII原则的典型应用,资源在对象构造时获取,在对象析构时释放,确保资源管理的正确性。 5. **智能指针的转换**:在某些情况下,不同...

    QT100-C++2010开发权威指南

    11. **RAII(Resource Acquisition Is Initialization)**:资源获取即初始化,是一种编程策略,常用于管理生命周期较短的资源,确保资源在不再需要时被正确释放。 12. **C++11/14/17/20新特性**:如lambda表达式、...

    Accerlerated-C++中文版习题答案(含书pdf)

    14. **异常安全性和资源管理**:讲解如何确保在异常发生时正确释放资源,以及RAII(Resource Acquisition Is Initialization)原则。 15. **C++11新特性**:如果习题答案涵盖C++11,那么可能会涉及lambda表达式、...

    C++资源管理器源程序.zip

    "C++资源管理器源程序"可能是一个用于学习或实际项目中的工具,它允许用户查看、操作和管理各种类型的资源。这个压缩包文件包含了实现这一功能的源代码,我们可以从中学习到C++中关于资源管理的一些核心概念和技术。...

    c++转换成c#代码

    - C#有`using`语句,用于资源的自动清理,类似C++的RAII(Resource Acquisition Is Initialization)。 8. **异步编程**: - C#通过`async/await`关键字支持异步编程,C++则需要使用回调或 futures。 9. **转换...

Global site tag (gtag.js) - Google Analytics