C++作为一门Native Langueages,在C++98/03时代,资源管理是个大问题。而内存管理又是其中最大的问题。申请的堆内存需要手动分配和释放,为了确保内存正确释放,一般原则是"谁分配谁负责释放",但软件工程的复杂性、程序员的编码水平参差不齐等仍然导致内存泄漏、空悬指针等问题。严重的内存泄漏可能很快导致服务器内存耗光而运行崩溃。

托管语言(JAVA、C#、C++ CLI等)为了解决这种问题引入了GC,把内存管理交给机器处理。而C++的解决办法一个是重启程序(内存泄漏严重时),一个就是今天的主角RAII(防止内存泄漏)。

Bjane最早提出RAII理念。 RAII全称(Resource Acquisition is Initialization),即对象构造时所需资源应在构造函数中初始化,对象析构时释放这些资源。这种范式意味着应该用类来封装和管理资源。现代C++提供的智能指针,正是用于实现RAII手法。智能指针是存储指向动态分配对象指针的类,用于生命期控制,能够确保智能指针离开作用域时,自动正确地销毁动态分配地对象,防止内存泄漏。正确地使用智能指针后,理论上程序中应该不会再出现delete,也不用担心内存泄漏问题了。毫不夸张地说,RAII是C++十几年积淀下来的真正好的部分之一。

RAII思想的一个技术应用就是scopeguard。

关于scopeguard技术的讨论见stackoverflow:

Does ScopeGuard use really lead to better code?

某老外激情答复:

If there is one single piece of C++ code that I could recommend every C++ programmer spend 10 minutes learning, it is ScopeGuard (now part of the freely available Loki library).

I decided to try using a (slightly modified) version of ScopeGuard for a smallish Win32 GUI program I was working on. Win32 as you may know has many different types of resources that need to be closed in different ways (e.g. kernel handles are usually closed with CloseHandle(), GDI BeginPaint() needs to be paired with EndPaint(), etc.) I used ScopeGuard with all these resources, and also for allocating working buffers with new (e.g. for character set conversions to/from Unicode).

What amazed me was how much shorter the program was. Basically, it's a win-win: your code gets shorter and more robust at the same time. Future code changes can't leak anything. They just can't. How cool is that?

大意就是使用loki库的scopeguard后,再也不会忘了释放资源了,大家赶快用。scopeguard的原理是当栈变量离开作用域时自动调用其析构函数,析构函数中调用用户编写的自定义释放资源代码。以windows操作系统为例,存在大量创建内核句柄的API,比如socket或beginpaint等,创建后立马调用scopeguard提供的方法,在里面closehandle或endpaint。

例如下面的代码,

用法1;

用法2:

输出结果:

清理资源代码紧跟着创建句柄代码,看着很清晰,而且会自动清理,用起来非常酷。下面从代码层面来剖析其实现原理。

SCOPE_GUARD_VARIABLES_AUTO_RUNNING_ON_EXIT宏的实现

 #ifndef ANONYMOUS_VARIABLES
#define ANONYMOUS_VARIABLES_IMPL(s1, s2) s1##s2
#define ANONYMOUS_VARIABLES(str) ANONYMOUS_VARIABLES_IMPL(str, __LINE__)
#endif
#ifndef SCOPE_GUARD_VARIABLES_AUTO_RUNNING_ON_EXIT
#define SCOPE_GUARD_VARIABLES_AUTO_RUNNING_ON_EXIT \
auto ANONYMOUS_VARIABLES(SCOPE_GUARD_EXIT_STATE) \
= ScopeGuardOnExit () + [&]() throw()//noexcept VS2013不支持noexcept

注意这里的_LINE_宏,作用是定义一个变量,变量名字后缀追加当前代码行号的数字。当用户使用下面的代码时

 SCOPE_GUARD_VARIABLES_AUTO_RUNNING_ON_EXIT{
printf("funcExcept:socket leave scope\n");
closesocket(hAcptSock);
};

编译期展开为

auto SCOPE_GUARD_EXIT_STATE13 = ScopeGuardOnExit () + [&]() throw(){
printf("funcExcept:socket leave scope\n");
closesocket(hAcptSock);
};

蓝字里的13是行号,蓝字的+是个重载的全局运算符,其定义如下

 template <typename FuncT>
ScopeGuardImpl<typename std::decay<FuncT>::type>
operator+ (ScopeGuardOnExit, FuncT&& func) {
return ScopeGuardImpl<typename std::decay<FuncT>::type>(
std::forward<FuncT>(func));
}

右侧的lanmba函数被作为参数传递为func,返回的是ScopeGuardImpl模板类的实例。这个实例SCOPE_GUARD_EXIT_STATE13是栈变量,其构造时,把lanmba函数带入。

 explicit ScopeGuardImpl(FuncT&& func)
: function_(std::move(func))
{
printf("ScopeGuardImpl(FuncT&& func) called");
}

当其销毁时,调用自己的析构函数时,进而调用lambda函数。

  ~ScopeGuardImpl()
{
if (!dismissed_) {
function_(); //ScopeGuardImpl析构时调用用户自己的方法
}
}

scopegaurd机制大致上如此。但scopeguard的使用是有局限的,如果代码块里的变量内存是用户在堆中new出来的,离开代码块前,你就得在scopeguard里delete 堆指针。但使用智能指针就不需要delete了,使用更方便。所以结论系统API或第三方库创建的资源使用完需要手写代码释放的,且创建和释放在同一个代码块的,用scopeguard管理。用户new出来的内存,使用智能指针管理。scopeguard+智能指针双重使用可保证彻底解决资源管理和内存管理问题。

另外,不管是loki库的scopeguard还是boost的或者第三方提供的类似技术,都无法保证在程序异常(指程序崩溃,而不是程序行为反常,不符合预期)时还能释放资源,并且也没有意义。程序异常了就关掉退出进程,资源自然就释放了。google家的编程规范禁止使用异常技术,程序异常了就crash,从dump文件中分析错误。

本人深以为然。

需要scopeguard源码实现的,请在评论里留下邮箱。

你应该掌握的C++ RAII手法:Scopegaurd的更多相关文章

  1. RAII手法封装互斥锁

    RAII手法是 Resource Acquisition is Initialization 的缩写,意为“资源获取即初始化”,在使用智能指针时也使用,下面是针对互斥量时的实现, #include & ...

  2. RAII手法封装相互排斥锁

    CriticalSectionWrapper是一个接口类 class CriticalSectionWrapper { public: // Factory method, constructor d ...

  3. C++ RAII手法实例,不使用智能指针

    /* * ===================================================================================== * * Filen ...

  4. C++中的RAII技法

    Resource Acquisition Is Initialization or RAII, is a C++ programming technique which binds the life ...

  5. C++11 auto_ptr 的问题

    auto_ptr作为最早的智能指针,可以实现以RAII手法管理堆区对象,但它设计的本意只是简单的利用C++对于栈区对象的自动析构管理堆区对象, 并不像shared_ptr那样包含引用计数,可以在每次拷 ...

  6. C++ stringstream介绍,使用方法与例子

    From: http://www.usidcbbs.com/read-htm-tid-1898.html C++引入了ostringstream.istringstream.stringstream这 ...

  7. 写一个Windows上的守护进程(5)文件系统重定向

    写一个Windows上的守护进程(5)文件系统重定向 在Windows上经常操作文件或注册表的同学可能知道,有"文件系统/注册表重定向"这么一回事.大致来说就是32位程序在64位的 ...

  8. 写一个Windows上的守护进程(3)句柄的管理

    写一个Windows上的守护进程(3)句柄的管理 在Windows中编程,跟HANDLE打交道是家常便饭.为了防止忘记CloseHandle,我都是使用do-while-false手法: void f ...

  9. 《Linux多线程服务端编程》笔记——线程同步精要

    并发编程基本模型 message passing和shared memory. 线程同步的四项原则 尽量最低限度地共享对象,减少需要同步的场合.如果确实需要,优先考虑共享 immutable 对象. ...

随机推荐

  1. 解决PHP使用POST提交数据不完整,数据不全的问题

    在后台form中,通过ajax请求返回了一个有很多input的form表单,提交数据后,要格式化数组时发现提交过来的数据不完整. PHP从5.3.9开始 php.ini 增加一个变量 max_inpu ...

  2. mysql实用函数

    1.  group_concat(); 可以将选择的字段列数据,分组以逗号分隔成一串.实用方便.select id,group_concat(distinct name) from ttt group ...

  3. 在chrome console添加jQuery支持

    有时候想在chrome console使用jq,那么下面这段代码就可以完美解决问题了. var script = document.createElement('script');script.src ...

  4. ntp时间同步参考

    https://blog.csdn.net/kamereon/article/details/54344114

  5. BuildTool

    (一)BuildTool是什么 BuildTool 构建工具  ,是一个把源代码生成可执行应用程序的过程自动化的程序(例如Android app生成apk).构建包括编译.连接跟把代码打包成可用的或可 ...

  6. extJs学习的资源

    http://www.qeefee.com/zt-extjs   Ext JS 6 入门学习资料大全(2016-12-14)   cddnExtJS学习:http://blog.csdn.net/co ...

  7. 微服务SpringCloud无法进行服务消费

    最近用SpringCloud做微服务,一直无法成功进行服务消费. 我使用的服务消费者是Feign,声明式调用服务提供者. 排查过程 1.检查服务提供者: (1)对提供的方法进行测试,确保提供的服务没有 ...

  8. 第一章 C++语言入门

            标准数据类型         C++语言提供了丰富的数据类型,如整数类型.实数类型(浮点数).字符类型等.每种数据类型均有均值范围,Dev-C++(4.9.9.2)是Windows平台 ...

  9. 移动端(处理边距样式)reset.css

    移动端reset.css,来自张鑫旭网站的推荐,下载地址:https://huruqing.gitee.io/demos/source/reset.css 代码 /* html5doctor.com ...

  10. 9.22 Sans-serif VS Serif

    在FCC做题遇到了sans-serif 以及 serif字体,第一次遇到,所以查了一下: 西方国家字母体系分为两类:serif 以及sans serif. 原来Sans-serif是无衬线字体,没有额 ...