vps 网站 需要绑定域名吗,关键词排名代发,安徽城乡建设部网站首页,潍坊推广平台前言
RAII的含义是“资源获取即初始化”。
一段看似安全的代码
首先看一段代码#xff1a; try{int *p new int[100];// ... do somethingdelete[] p; }catch(exception e){ // ..... }
这段代码中#xff0c;我们先进行了动态内存分配#xff0c;使…
前言
RAII的含义是“资源获取即初始化”。
一段看似安全的代码
首先看一段代码 try{int *p new int[100];// ... do somethingdelete[] p; }catch(exception e){ // ..... }
这段代码中我们先进行了动态内存分配使用完释放看起来很完美但是这段程序是否真的保证不会发生内存泄漏
考虑这样一种情形程序在使用这段内存的过程中throw一个异常于是程序转向catch块然后XXX。 这段内存被释放了吗 显然没有。 那么这段程序应该从哪里改进呢
对象的生命期
考虑一个问题C中对象的声明期是怎样的在C中对象创建的方式有两种一种是栈上一种是堆上创建。 { Animal a; // stack Animal *pa new Animal(); // heap }
上面的代码中第一个对象创建在栈上更明确的说法是它是一个局部变量这意味着它的生命期起源于被创建的这行语句终结与所在作用域的末尾也就是这里的右花括号}。 而第二个对象呢它是采用所谓的动态内存分配生成的需要程序员手工去释放当调用delete的时候才销毁但调用delete的时机不是固定的。 也就是说栈对象的生命期是明确的而堆对象的生命期由于取决于调用delete的时机因而是不明确的。
看到这里之前那段有可能内存泄漏的代码如何去改进呢
答案就是用栈对象明确的生命期去管理资源。
用对象的生命期管理资源
试想一下如果我们把之前程序中对内存的分配写在构造函数中把释放资源写在析构函数中而栈对象的生命期是明确的当该管理资源的对象过期时连同它管理的资源一起释放岂不是非常智能化
我们尝试着写出下列代码 class ScopePtr{ public: ScopePtr(int *p):_p(p){} ~ScopePtr(){ delete[] _p; } private: int *_p; };
我们把之前的代码做如下的改进 try{ScopePtr scope(new int[100]);// ... do something}catch(exception e){ // ..... }
再来分析一下这段代码 如果正常执行那么当执行完try块时scope对象过期执行析构函数同时释放了那段数组。如果使用的过程中发生了异常那么当程序进入catch块时同样会销毁try内的局部变量。 无论是哪种情况内存总是会被释放。 如果这里不是int而是其他复杂的类型使用这个封装的ScopePtr是不是不太方便显然不会我们去重载成员操作符就可以了使它表现的像个指针这就是一个最简单的智能指针的产生。 问题得到了完美的解决
资源获取即初始化
我们上面解决问题的办法就是RAII技术RAII的含义是“资源获取即初始化”这个概念有两个要点
获得资源后立即放进管理对象管理对象运用析构函数确保资源被释放
看另外一个例子我们在访问一些临界区资源的时候通常需要加锁所以产生了下面的代码 { mutex.lock(); //do sth..mutex.unlock(); }
这种方式是很容易出现问题的例如程序中间遇见错误情况需要退出这个函数此时很容易忘记解锁 { mutex.lock(); //do sth.. if(...){ return false // forgot to unlock }// ... mutex.unlock(); }
此时如果再次进行Lock操作就造成了死锁。 解决这个问题的办法仍然很简单我们去写一个类
class MutexLockGuard{public: MutexLockGuard(MutexLock mutex):_mutex(mutex){ _mutex.lock(); } ~MutexLockGuard(){ _mutex.unlock(); }private: MutexLock _mutex;};
这样刚才那段代码就可以修改成 { MutexLockGuard guard(lock); //do sth.. if(...){ return false }// ... }
这样一旦离开这段代码程序立刻自动解锁。 不过为了防止错误使用这个类例如 MutexLockGuard(lock);
可以定义一个宏
#define MutexLockGuard(m) ERR MutexLockGuard
这样我们在错误使用的时候编译期间就能发现错误。
一种泛型解决方案
刘未鹏在他的《C11及现代C风格和快速迭代式开发》中提出了一种泛型实现利用了C11的function和Lambda匿名函数如下
class ScopeGuard{public: explicit ScopeGuard(std::functionvoid() onExitScope) : onExitScope_(onExitScope), dismissed_(false) { }~ScopeGuard() { if(!dismissed_) { onExitScope_(); } }void Dismiss() { dismissed_ true; }
private: std::functionvoid() onExitScope_; bool dismissed_;
private: // noncopyable ScopeGuard(ScopeGuard const); ScopeGuard operator(ScopeGuard const);};
使用方式也很简单
HANDLE h CreateFile(...);ScopeGuard onExit([] { CloseHandle(h); });
其实就是将该资源释放的函数代码段注册到Scope类其中原理不再赘述。
与其他语言的对比
RAII是C独有的编程手段。通过RAII技术我们能够做到资源不需要使用时立即释放这是其他GC语言所不具备的。 以Java为例Java具有完善的GCGarbage Collection垃圾回收机制但是存在如下的缺点
GC只能回收内存而对于打开的文件、数据库连接等仍然需要手工关闭。GC因为进程优先级等原因回收效率底下详情可以参考孟岩的《垃圾收集机制(Garbage Collection)批判》
conclusion
RAII技术是现代C编程技术中及其重要的一部分甚至有人称其为“C编程中最重要的编程技法”可见其重要性。通过RAII我们完全可以实现资源的自动化管理写出永不内存泄漏的程序。
参考资料
《C Primer》《Effective C》《Linux多线程服务器端编程》