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

转载请说明出处:http://blog.csdn.net/cywosp/article/details/29585227


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:
    voidoperator 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



你可能感兴趣的:(C++)