你应该掌握的C++ RAII手法:Scopegaurd
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的更多相关文章
- RAII手法封装互斥锁
RAII手法是 Resource Acquisition is Initialization 的缩写,意为“资源获取即初始化”,在使用智能指针时也使用,下面是针对互斥量时的实现, #include & ...
- RAII手法封装相互排斥锁
CriticalSectionWrapper是一个接口类 class CriticalSectionWrapper { public: // Factory method, constructor d ...
- C++ RAII手法实例,不使用智能指针
/* * ===================================================================================== * * Filen ...
- C++中的RAII技法
Resource Acquisition Is Initialization or RAII, is a C++ programming technique which binds the life ...
- C++11 auto_ptr 的问题
auto_ptr作为最早的智能指针,可以实现以RAII手法管理堆区对象,但它设计的本意只是简单的利用C++对于栈区对象的自动析构管理堆区对象, 并不像shared_ptr那样包含引用计数,可以在每次拷 ...
- C++ stringstream介绍,使用方法与例子
From: http://www.usidcbbs.com/read-htm-tid-1898.html C++引入了ostringstream.istringstream.stringstream这 ...
- 写一个Windows上的守护进程(5)文件系统重定向
写一个Windows上的守护进程(5)文件系统重定向 在Windows上经常操作文件或注册表的同学可能知道,有"文件系统/注册表重定向"这么一回事.大致来说就是32位程序在64位的 ...
- 写一个Windows上的守护进程(3)句柄的管理
写一个Windows上的守护进程(3)句柄的管理 在Windows中编程,跟HANDLE打交道是家常便饭.为了防止忘记CloseHandle,我都是使用do-while-false手法: void f ...
- 《Linux多线程服务端编程》笔记——线程同步精要
并发编程基本模型 message passing和shared memory. 线程同步的四项原则 尽量最低限度地共享对象,减少需要同步的场合.如果确实需要,优先考虑共享 immutable 对象. ...
随机推荐
- 【HQL】小技巧
case1.a与b匹配表保留一条匹配关系 背景:匹配b,b匹配a在同一张表: match_table表为: uid,m_uid 111,222 222,111 需求:只保留一条匹配关系. 结果为: u ...
- 遇到问题或bug时要做的事。
1,做事细心,只有细心才能减少bug量,做总结. 2,开发中遇到bug和错误,第一要想到是程序代码的问题.而首先想到的不是其他问题(比如版本,框架或兼容问题等). 3,程序不能按照自己的意愿执行,时先 ...
- mazing ASP.NET Core 2.0【转】
前言 ASP.NET Core 的变化和发展速度是飞快的,当你发现你还没有掌握 ASP.NET Core 1.0 的时候, 2.0 已经快要发布了,目前 2.0 处于 Preview 1 版本,意味着 ...
- win7、centos7 双系统安装总结
centos7安装过程 问题:TroubleShooting选项进入图形化界面安装才成功. win7恢复引导区 问题:安装完Centos后,win7的引导区不见了 具体恢复过程:http://www. ...
- centos7安装mysql客户端
1.判断是否已安装 [root@k8s-master master]# which mysql /usr/bin/which: no mysql in (/usr/local/sbin:/usr/lo ...
- java Integer类以及拆箱和装箱
package com.ilaw.boson.controller; public class Demo { public static void main(String[] args) { Inte ...
- Linux下编译安装FFmpeg
FFmpeg官网:http://www.ffmpeg.org 官网介绍 FFmpeg is the leading multimedia framework, able to decode, enco ...
- ios中设置input为readonly后,解决弹起软键盘的问题
可以在input中添加unselectable="on" onfocus="this.blur()",可以解决软键盘弹起问题 <input type=&q ...
- HTTPS协议、TLS协议、证书认证过程解析
一.HTTPS 协议 HTTPS协议其实就是HTTP over TSL,TSL(Transport Layer Security) 传输层安全协议是https协议的核心. TSL可以理解为SSL (S ...
- 学JS的心路历程 - JS应用
各家电商网站都推出了各种活动和现今优惠券,当时在逛PTT时看到了有篇文章,提供代码教大家用JS的方式抢票,看了一下后发现好像很多人好奇这是怎么做的,于是就想说想一篇文章来讲解一下. 我们先来看一下折价 ...