您的位置:首页 > 编程语言 > C语言/C++

每天进步一点点——C++11中使用lambda表达式实现一个简单的ScopeGuard

2014-06-09 15:56 531 查看
转载请说明出处:/article/7618585.html

1. RAII
在进入本文主要内容之前先了解一下C++中常用的RAII(Resource acquisition is initialization)技术,即资源获取即初始化。其利用C++对象生命周期的概念来控制 程序的资源,例如内存、文件句柄、网络连接以及审计追踪(audit
trail)等。RAII的基本技术原理很简单,若希望保持对某个重要资源的跟踪,那么创建一个对象,并将资源的生命周期和对象的生命周期相关联,如此一来,就可以利用C++复杂老练的对象管理设施来管理资源。最简单的RAII形式是创建这样一个对象:其构造函数获取一份资源,而析构函数则释放这份资源。在最新的C++标准中有如下数据类型:std::lock_guard、std::unique_lock、std::shared_ptr、std::unique_ptr等等都是RAII技术的很好体现,同时我们也可以自己现实对相关资源的初始化以及释放,例如在多线程编程中我们往往会忘记释放锁而导致线程死锁:

class Mutex
{
public:
Mutex () {pthread_mutex_init (&mu_, NULL);}
~Mutex () {pthread_mutex_destroy (&mu_);}
void lock () {pthread_mutex_lock (&mu_);}
void unlock () {pthread_mutex_unlock (&mu_);}

private:
pthread_mutex_t mu_;

// No copying
Mutex (const Mutex&);
void operator= (const Mutex&);
};

Mutex lock;
{
std::lock_guard<Mutex> guard (lock); //
lock is locked by guard
……
}
// lock is not locked by anyone

……

2. Lambda表达式
C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。Lambda的语法形式如下:
[函数对象参数] (操作符重 载函数参数) mutable或exception声明;返回值类型 {函数体};

可以看到,Lambda主要分为五个部分:[函数对象参数]、(操作符重载函数参数)、mutable或exception声明、返回值类型、{函数体}。下面分别进行介绍。
一、[函数对象参数],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定 义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:
1、[] 没有使用任何函数对象参数。
2、[=] 函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
3、[&] 函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
4、[this] 函数体内可以使用Lambda所在类中的成员变量。
5、[a] 将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
6、[&a] 将a按引用进行传递。
7、[a, &b] 将a按值进行传递,b按引用进行传递。
8、[=,&a, &b] 除a和b按引用进行传递外,其他参数都按值进行传递。
9、[&, a, b] 除a和b按值进行传递外,其他参数都按引用进行传递。

二、(操作符重载函数参数),标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。

三、mutable或exception声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw (int)。

四、返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。

五、{函数体},标识函数的实现,这部分不能省略,但函数体可以为空。

3. ScopeGuard的实现
有了上面的基础,我们就可以根据相关知识来实现ScopeGuard的了,具体实现如下:

class ScopeGuardBase {
public:
inline void Dismiss () noexcept
{ dismissed_ = true; }

protected:
ScopeGuardBase () : dismissed_ (false) { }
ScopeGuardBase (ScopeGuardBase&& other)
: dismissed_ (other.dismissed_) {
other.dismissed_ = true;
}

bool dismissed_;
};

template <typename FuncT>
class ScopeGuardImpl : public ScopeGuardBase
{
public:
explicit ScopeGuardImpl (const FuncT&
func) : function_ (func) { }
explicit ScopeGuardImpl (FuncT&& func) :
function_ (std::move (func)) { }

ScopeGuardImpl (ScopeGuardImpl&& other)
: ScopeGuardBase (std::move (other))
, function_ (std::move (other.function_)) {
other.dismissed_ = true;
}

~ScopeGuardImpl () {
if (!dismissed_) {
function_ ();
}
}

private:
void* operator new(size_t)
= delete;
FuncT function_;
};

typedef ScopeGuardBase&& ScopeGuard;

看到这里你可能已经明白大概了,其实现原理就是利用析构函数来执行注册进来的func函数,以达到释放资源的目的。继续看下面的代码:
enum class ScopeGuardOnExit {};

template <typename FuncT>

ScopeGuardImpl<typename std::decay<FuncT>::type>

operator+ (ScopeGuardOnExit, FuncT&& func) {

return ScopeGuardImpl<typename
std::decay<FuncT>::type> (

std::forward<FuncT> (func));

}

template <typename FuncT>

ScopeGuardImpl<typename std::decay<FuncT>::type>

MakeScopeGuard (FuncT&& func) {

return ScopeGuardImpl<typename std::decay<FuncT>::type>
(

std::forward<FuncT> (func));

}

// define a anonymous variables with the code line and a str

#ifndef SWIFT_ANONYMOUS_VARIABLES

#define SWIFT_ANONYMOUS_VARIABLES_IMPL(s1,
s2) s1##s2

#define SWIFT_ANONYMOUS_VARIABLES(str) SWIFT_ANONYMOUS_VARIABLES_IMPL(str, __LINE__)

#endif

#ifndef SCOPE_GUARD_VARIABLES_AUTO_RUNNING_ON_EXIT

#define SCOPE_GUARD_VARIABLES_AUTO_RUNNING_ON_EXIT \

auto SWIFT_ANONYMOUS_VARIABLES(SCOPE_GUARD_EXIT_STATE)
\

= ScopeGuardOnExit () + [&]() noexcept

#endif

一般情况下,我们会隐藏ScopeGuardImpl的实现,使得外界无法调用,因此我们提供MakeScopeGuard函数来创建ScopeGuard对象。为了使用方便提供宏
SCOPE_GUARD_VARIABLES_AUTO_RUNNING_ON_EXIT来处理,使用方法如下:
SCOPE_GUARD_VARIABLES_AUTO_RUNNING_ON_EXIT {
// your code to release some resource.
};
下面来看一个读写文件的例子:
template <class Container>
bool ReadFile (const
char *file_name,
Container& out,
size_t num_bytes = std::numeric_limits<size_t>::max ()) {
static_assert(sizeof(out[0])
== 1, "ReadFile: only containers with byte-sized elements accepted");
assert (file_name);
const int fd = ::open (file_name, O_RDONLY);
if (-1 == fd) { return
false; }

size_t size = 0;
SCOPE_GUARD_VARIABLES_AUTO_RUNNING_ON_EXIT {
assert (out.size () >= size);
out.resize (size);
Close (fd);
};

// Get file size
struct stat buf;
if (-1 == ::fstat (fd, &buf)) { return
false; }

// Some files (notably under /proc
and /sys on Linux) lie about
// their size, so treat the size advertised by fstat under advise
// but don't rely on it. In particular, if the size is zero, we
// should attempt to read stuff. If not zero, we'll attempt to
read
// one extra byte.
const size_t initial_alloc = 1024 * 4;
out.resize (std::min (buf.st_size > 0 ? static_cast<size_t>(buf.st_size
+ 1) : initial_alloc,
num_bytes));
while (size < out.size ()) {
size_t n = read (fd, &out[size], out.size () - size);
if (-1 == n) { return
false; }
size += n;
if (size < out.size ()) { break; }
// Ew, allocate more memory.
Use exponential growth to avoid
// quadratic behavior. Cap size to num_bytes.
out.resize (std::min (out.size () * 3 / 2, num_bytes));
}

return true;
}
该例子中使用SCOPE_GUARD_VARIABLES_AUTO_RUNNING_ON_EXIT宏定义了一个局部变量,变量析构时会自动调用关闭文件描述符的函数,因此使得代码更加简洁明了。更详细的使用方式,请参考https://github.com/ApusApp/Swift/tree/master/swift/base/scopeguard.h的实现及其单元测试。

参考
[1]. http://www.cnblogs.com/hujian/archive/2012/02/14/2350306.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: