在之前一篇文章<<从lock_guard来说一说C++中常用的RAII>> 讲解了RAII
, 其实一种常见的资源管理方式,减少了资源泄露的风险。 同事和我说是不是就是智能指针, 准确来说RAII是一种思想,一般是利用栈上对象初始化进行资源的申请,在其生命周期结束的时候,自动调用其析构函数,对资源进行释放。 比如std::string
, std::lock_guard
都属于RAII的一种实现,那么对于不同资源的管理我是否都要实现一个类似于std::lock_guard
一样的实现,其实不然,这样写代码多么费劲。那么有没有类似于golang中defer
的实现呢,在函数退出的时候,自动调用一些代码,比如实现资源释放?是可以的,我们一起来看一看吧。
golang中的defer
golang
的一段代码如下,这段代码比较简单,就是打开文件,然后读取文件内容。我们需要关注的是defer
这一样,这一段可以表明在函数退出的时候会调用fileObj.Close()
去关闭文件。
func ReadFile() {
//Step 0: Open file
strFileName := "golangtest.txt"
fileObj, err := os.Open(strFileName)
if err != nil {
fmt.Println("Open file Failed: ", strFileName)
return
}
//Step 1: Set defer to close
defer fileObj.Close()
//Step 3: Read file
reader := bufio.NewReader(fileObj)
buffer := make([]byte, 100)
_, err = reader.Read(buffer)
if err != nil {
fmt.Println("Read file Failed: ", strFileName)
return
}
fmt.Println("Read file Content: ", string(buffer))
}
当然了defer也可以指定一个函数去执行多行指令比如改成:
defer func() {
fmt.Println("Close file: ", strFileName)
fileObj.Close()
}()
这个功能如果在C++
中使用也一定很棒,这样当打开文件之后,在C++
中也用这个defer去关闭文件,就不用管后续有多少个return,多少个触发异常的地方,从而忘记关闭文件了。
C++中的defer实现
在C++ 11出来之后有了Lamdba
之后实现defer
更加便捷了。我们继续使用<<从lock_guard来说一说C++中常用的RAII>>中的例子来。回顾下述代码的问题。
- 在互斥区的代码有可能会有多处返回return, 在每个return处都加上mutex.unlock()代码感觉显得很不优雅。
- 互斥区代码也有可能抛出异常,而有些场景,你并不想在互斥区捕获异常,那么也就不会调用mutext.unlock()从而导致锁并没有释放。
在之前的文章我们描述过可以通过std::lock_guard
来实现RAII,保证资源总是在函数退出时候释放锁。那么我们用defer
如何来实现呢?
void function()
{
mutex.lock();
//互斥区执行代码;
//...
if (...)
{
//...
mutex.unlock();
return;
}
//...
if (...)
{
//...
mutex.unlock();
return;
}
//...
mutex.unlock();
}
我们先实现一个RAIIDefer
的类, RAIIDefer
的类接受的初始化为一个std::function<void()>
参数.而我们通过lambda
可以便捷引用函数内部的变量,这个lamda
的对象,做为RAIIDefer
的构造函数参数传入进去,并且在析构的时候调用,从而可以做到在函数退出的时候实现资源释放。
class RAIIDefer
{
public:
RAIIDefer(std::function<void()> fDeferFunction) {
m_fDeferFunction = fDeferFunction;
}
~RAIIDefer() {
if (m_fDeferFunction)
{
m_fDeferFunction();
}
}
private:
RAIIDefer() {
};
std::function<void()> m_fDeferFunction;
};
void function()
{
std::mutex mutex;
mutex.lock();
RAIIDefer defer ( [&] {
mutex.unlock();
}
);
// Do something else
// ......
}
当然我们还可以优化下,和golang中的defer
更像一点。这里宏我简单解释下,主要就是为了让其定义局部变量名字加上一个行号,从而避免名字冲突。
// 参考刘未鹏: http://mindhacks.cn/2012/08/27/modern-cpp-practices/
#define DEFER_LINENAME_CAT(name, line) name##line
#define DEFER_LINENAME(name, line) DEFER_LINENAME_CAT(name, line)
#define defer(deferFunction) RAIIDefer DEFER_LINENAME(DEFER_NAME_, __LINE__)(deferFunction)
class RAIIDefer
{
public:
RAIIDefer(std::function<void()> fDeferFunction) {
m_fDeferFunction = fDeferFunction;
}
~RAIIDefer() {
if (m_fDeferFunction)
{
m_fDeferFunction();
}
}
private:
RAIIDefer() {
};
std::function<void()> m_fDeferFunction;
};
void function()
{
std::mutex mutex;
mutex.lock();
defer ( [&] {
mutex.unlock();
}
);
// Do something else
// ......
}