【C++设计技巧】C++中的RAII机制

作者:gnuhpc 
出处:http://www.cnblogs.com/gnuhpc/

1.概念

Resource Acquisition Is Initialization 机制是Bjarne Stroustrup首先提出的。要解决的是这样一个问题:

在C++中,如果在这个程序段结束时需要完成一些资源释放工作,那么正常情况下自然是没有什么问题,但是当一个异常抛出时,释放资源的语句就不会被执行。于是Bjarne Stroustrup就想到确保能运行资源释放代码的地方就是在这个程序段(栈帧)中放置的对象的析构函数了,因为stack winding会保证它们的析构函数都会被执行。将初始化和资源释放都移动到一个包装类中的好处:

  • 保证了资源的正常释放
  • 省去了在异常处理中冗长而重复甚至有些还不一定执行到的清理逻辑,进而确保了代码的异常安全。
  • 简化代码体积。

2.应用场景

1)文件操作

我们可以是用这个机制将文件操作包装起来完成一个异常安全的文件类。实现上,注意将复制构造函数和赋值符私有化,这个是通过一个私有继承类完成的,因为这两个操作在此并没有意义,当然这并不是RAII所要求的。

/** =====================================================================================**       Filename:  file.cpp**    Description:  RAII for files**        Version:  1.0*        Created:  05/09/2011 06:57:43 PM*       Revision:  none*       Compiler:  g++**         Author:  gnuhpc, warmbupt@gmail.com** =====================================================================================*/
#include <IOSTREAM>
#include <STDEXCEPT>
#include <CSTDIO>using namespace std;
class NonCopyable
{
public:
NonCopyable(){};
private:NonCopyable (NonCopyable const &); // private copy constructorNonCopyable & operator = (NonCopyable const &); // private assignment operator
};class SafeFile:NonCopyable{
public:SafeFile(const char* filename):fileHandler(fopen(filename,"w+")){if( fileHandler == NULL ){throw runtime_error("Open Error!");}}~SafeFile(){fclose(fileHandler);}void write(const char* str){if( fputs(str,fileHandler)==EOF ){throw runtime_error("Write Error!");}}void write(const char* buffer, size_t num){if( num!=0 && fwrite(buffer,num,1,fileHandler)==0 ){throw runtime_error("Write Error!");}}
private:FILE *fileHandler;SafeFile(const SafeFile&);SafeFile &operator =(const SafeFile&);
};int main(int argc, char *argv[])
{SafeFile testVar("foo.test");testVar.write("Hello RAII");
}

C++的结构决定了其原生支持RAII,而在Java 中,对象何时销毁是未知的,所以在Java 中可以使用try-finally做相关处理。

2)智能指针模拟

一个更复杂一点的例子是模拟智能指针,抽象出来的RAII类中实现了一个操作符*,直接返回存入的指针:

现在我们有一个类:

class Example {SomeResource* p_;SomeResource* p2_;
public:Example() :p_(new SomeResource()),p2_(new SomeResource()) {std::cout << "Creating Example, allocating SomeResource!/n";}Example(const Example& other) :p_(new SomeResource(*other.p_)),p2_(new SomeResource(*other.p2_)) {}Example& operator=(const Example& other) {// Self assignment?if (this==&other)return *this;*p_=*other.p_;*p2_=*other.p2_;return *this;}~Example() {std::cout << "Deleting Example, freeing SomeResource!/n";delete p_;delete p2_;}
};

假设在创建SomeResource的时候可能会有异常,那么当p_指向的资源被创建但p2_指向的资源创建失败时,Example的实例就整个创建失败,那么p_指向的资源就存在内存泄露问题。

用下边的这个方法可以为权宜之计:

Example() : p_(0),p2_(0)
{try {p_=new SomeResource();p2_=new SomeResource("H",true);std::cout << "Creating Example, allocating SomeResource!/n";}catch(...) {delete p2_;delete p_;throw;}
}

但是我们可以利用一个对象在离开一个域中会调用析构函数的特性,在构造函数中完成初始化,在析构函数中完成清理工作,将需要操作和保护的指针作为成员变量放入RAII中。

template <TYPENAME T>
class RAII {T* p_;
public:explicit RAII(T* p) : p_(p) {}~RAII() {delete p_;}void reset(T* p) {delete p_;p_=p;}T* get() const {return p_;}T& operator*() const {return *p_;}void swap(RAII& other) {std::swap(p_,other.p_);}private:RAII(const RAII& other);RAII& operator=(const RAII& other);
};

我们在具体使用把保护的指针Someresource放在RAII中:

class Example {RAII<SOMERESOURCE> p_;RAII<SOMERESOURCE> p2_;
public:Example() :p_(new SomeResource()),p2_(new SomeResource()) {}Example(const Example& other): p_(new SomeResource(*other.p_)),p2_(new SomeResource(*other.p2_)) {}Example& operator=(const Example& other) {// Self assignment?if (this==&other)return *this;*p_=*other.p_;*p2_=*other.p2_;return *this;}~Example() {std::cout << "Deleting Example, freeing SomeResource!/n";}
};

现在即使p_成功而p2_失败,那么在Stack winding时也会调用RAII的析构函数保证了p_指向的Someresource被析构。这种方法较之例1中需要实现被组合的指针类型相应的接口不同,这里不需要对接口进行封装。当然,在例1中,你也可以提供一个getPointer的函数直接将句柄提供出来。

其实在Example中,已经不需要析构函数了,因为RAII类会帮它照顾好这一切的。这有点像auto_ptr,本文并不打算深入讨论智能指针这个话题。

3)锁操作

/** =====================================================================================**       Filename:  threadlock.cpp**    Description:  Lock for RAII**        Version:  1.0*        Created:  05/09/2011 10:16:13 PM*       Revision:  none*       Compiler:  g++**         Author:  gnuhpc (http://blog.csdn.net/gnuhpc), warmbupt@gmail.com** =====================================================================================*/
#include <CSTDIO>
#include <STDLIB.H>
#include <PTHREAD.H>int counter = 0;
void* routine(void *ptr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;class NonCopyable
{
public:NonCopyable(){};
private:NonCopyable (NonCopyable const &); // private copy constructorNonCopyable & operator = (NonCopyable const &); // private assignment operator
};class ScopeMutex:NonCopyable
{
public:ScopeMutex(pthread_mutex_t* mutex):mutex_(mutex){pthread_mutex_lock( mutex_ );}~ScopeMutex(){pthread_mutex_unlock( mutex_ );}
private:pthread_mutex_t *mutex_;
};int main(int argc, char *argv[])
{int rc1, rc2;pthread_t thread1, thread2;if( (rc1=pthread_create( &thread1, NULL, routine, NULL)) ){printf("Thread creation failed: %d/n", rc1);}if( (rc2=pthread_create( &thread2, NULL, routine, NULL)) ){printf("Thread creation failed: %d/n", rc1);}pthread_join( thread1, NULL);pthread_join( thread2, NULL);
}void* routine(void *ptr)
{ScopeMutex scopeMutex(&mutex);counter++;printf("%d/n",counter);
}

3.总结

RAII机制保证了异常安全,并且也为程序员在编写动态分配内存的程序时提供了安全保证。缺点是有些操作可能会抛出异常,如果放在析构函数中进行则不能将错误传递出去,那么此时析构函数就必须自己处理异常。这在某些时候是很繁琐的。

4.参考文献

http://www.codeproject.com/KB/cpp/RAIIFactory.aspx  这篇文章用工厂方法的方式完成了一个RAII工厂。

http://www.informit.com/articles/printerfriendly.aspx?p=21084 讨论了异常安全的一些情况,其中提到赋值符的安全值得注意。

 

作者:gnuhpc 
出处:http://www.cnblogs.com/gnuhpc/

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

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

相关文章

PHP的表单获取与HHTP请求方式

PHP获取表单数据的方法PHP获取表单数据的方法主要有GET、POST和REQUEST三种方式。 注意&#xff0c;提交表单的方法只有POST和GET两种。POST和GET都可以向Web服务器传输数据。 GET与POST对比 对比GETPOST附加形式URL之后HTML HEADER参数处理方式URL编码无URL编码传输数据的大小…

产生脉冲之间的延迟

产生脉冲之间的延迟 题目要求 FPGA产生两个输出脉冲&#xff0c;要求这两个脉冲之间的延迟为0.5ns&#xff0c;请描述你的实现方案 题目分析 1 使用计数器延迟不太可能&#xff0c;FPGA内部逻辑没有一个专用的delaybuffer。 2 IOB (input output block) iodelay。去实现小分辨…

Chipscope使用

作者&#xff1a;桂。 时间&#xff1a;2017-08-07 06:47:31 链接&#xff1a;http://www.cnblogs.com/xingshansi/p/7297482.html 前言 Chipscope在FPGA调试中被用来观察内部信号&#xff0c;程序里加入Chipscope相当于有了数字示波器。 本文主要记录Chipscope的基本操作&…

学习网址汇总

编程相关的学习网站&#xff1a;网络编程网站开发教程大全&#xff0c;内含有大量的参考手册&#xff0c;博文阅读&#xff0c;优质项目、在线工具、在线代码、开放平台等&#xff0c;囊括PHP、SQL、HTML、JS等w3school&#xff0c;你可以找到你所需要的所有的网站建设教程&…

设计FIFO深度

设计FIFO深度 假设FIFO的写时钟为100MHZ&#xff0c;读时钟为80MHZ。在FIFO输入侧写数据侧&#xff0c;每100个时钟&#xff0c;写入80个数据&#xff0c;FIFO读出侧每个时钟读出一个数据。设计合理的FIFO深度&#xff0c;使FIFO不会溢出。 如果读出侧每3个时钟读出1个数据&am…

银行假流水怎么识破?

银行流水作为借款人最有效的还款证明&#xff0c;审核的时候一定要特重视&#xff0c;特别是要注意区分借款人的假流水。&#xff08;1&#xff09;真假流水有哪些特征真流水特征&#xff1a;银行流水能真实的反映借款人的工作或生意情况&#xff0c;能从借款人的生意来往合同中…

无线通信上课笔记

无线通信课上笔记无线通信的概念无线通信&#xff08;Wireless Comminication&#xff09;是利用电磁波信号可以在自由空间中传输的特性进行信息交换的一种通信方式&#xff0c;无线通信与有线通信相对。特点&#xff1a;新、快、移动通信&#xff1a;在移动中实现无线通信&…

Qt5 中的 signal/slot 新语法

Qt 5 Alpha 已经发布。我们会在后面的文章中看到 Qt 5 的新变化。今天&#xff0c;我们先来看一下 Qt 5 带来的一个最主要的变化&#xff1a;signal/slot 机制的改变。Qt 5 之前的语法 在 Qt 5 之前&#xff0c;我们需要使用下面的语句来链接 signal 和 slot&#xff1a; 12con…

模拟电子技术基础笔记

模拟电子技术基础笔记 二极管的特性 1 单向导电性&#xff08;小写是交流&#xff0c;大写是直流&#xff09; 二极管的应用 1、整流二极管 利用二极管单向导电性&#xff0c;可以把方向交替变化的交流电变换成单一方向的脉动直流电。 2、开关元件 二极管在正向电压作用下电阻…

国家标准GB7665-87传感器

传感器的定义“能感受规定的被测量并按照一定的规律转换成可用信号的器件或装置&#xff0c;通常由敏感元件和转换元件组成”。 传感器是一种检测装置&#xff0c;能感受到被测量的信息&#xff0c;并能将检测感受到的信息&#xff0c;按一定规律变换成为电信号或其他所需形式的…

simulink简介

simulink简介 simulink是基于matlab的框图设计环境&#xff0c;可以用来对各种动态系统进行建模、分析和仿真&#xff0c;它的应用领域十分广泛&#xff0c;任何能用数学模型来描述的系统都可以在simulink中进行仿真分析&#xff0c;如:空气动力学、导航制导、通讯、电子、机械…

商务英语老师给的6个建议

作为学了多年学习英语&#xff0c;应该做这些研读两套英语教材&#xff08;大学英语剑桥商务英语&#xff09;读两本小薄本&#xff08;两个英语原著&#xff09;独立看两部英语原版电影学习一个国外的语言文化&#xff0c;至少会唱两首英文歌&#xff08;圣诞歌会唱&#xff0…

C++ 11 在 Qt 5 中的应用

C 11 现在已经是 C 标准&#xff0c;也就没有理由不在新的应用中使用。Qt 4.8 是第一个支持 C 11 特性的 Qt 版本&#xff0c;不过这里&#xff0c;我们首先介绍的是&#xff0c;Qt 5 中如何结合使用 C 11。至于 Qt 4.8&#xff0c;我们会在后续文章中进行阐述。 显而易见的是&…

多媒体表示、描述、编码以及通信的相关规定和标准课程总结

时间&#xff1a;2016.10.11-2016. 11.3注意关注会议机构ITU&#xff08;国际电信联盟&#xff09; MPEG&#xff1a; Moving Picture Experts Group&#xff0c;动态图像专家组, 是ISO&#xff08;International Standardization Organization&#xff0c;国际标准化组织&…

建模与仿真的流程

建模与仿真的流程 以例子为例 第一步&#xff1a;建立模型&#xff0c;列出输出表达式 第二步&#xff1a;解出输出方程 可以参考&#xff1a;解单位冲激 第三步&#xff1a;在simulink中建立仿真图 ctrlr&#xff1a;表示旋转90 按住ctrl可以引出多条线 点击保存&…

AD制图相关问题总结

AD制图总体步骤新建一个工程文件File ->New->Project->PCB.Project->save新建原理图在此工程目录下新建原理图与PCB并保存 File ->New->Schematic->save生成PCB&#xff08;前提是原理图已完成且没有错误&#xff09;File->New-> PCB->save&…

vcs 入门

synopsys vcs 软件是仿真和验证的软件&#xff0c;必须掌握。 vcs 即 verilog compile simulator 支持 verilog, systemVerilog, openvera, systemC等语言&#xff0c;同时也有代码覆盖率检测等功能。 可以合第三方软件集合使用&#xff0c;例如 Vera , Debussy &#xff0c;S…

输入输出及子模块

输入输出及子模块 常用模块库 1 事先规划好功能子系统 2 建立好系统后对系统进行功能划分 在进行动态系统的建模过程中&#xff0c;可以根据需要将模型中比较复杂&#xff0c;或者共同完成某一功能的基本模块&#xff08;低一层次的子系统&#xff09;封装起来&#xff0c;并…

使用Hexo搭建博客步骤详解

简介hexo —— 简单、快速、强大的Node.js静态博客框架。Hexo 使用 Markdown&#xff08;或其他渲染引擎&#xff09;解析文章&#xff0c;在几秒内&#xff0c;即可利用靓丽的主题生成静态网页。 特性风一般的速度Hexo基于Node.js&#xff0c;支持多进程&#xff0c;几百篇文章…

32个最热CPLD-FPGA论坛

1. OPENCORES.ORG 这里提供非常多&#xff0c;非常好的PLD了内核&#xff0c;8051内核就可以在里面找到。进入后&#xff0c;选择project或者由http//www.opencores.org/browse.cgi/by_category进入。对于想了解这个行业动态人可以看看它的投票调查。http://www.opencores.org/…