前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++ RAII实现golang的defer

C++ RAII实现golang的defer

作者头像
河边一枝柳
发布2021-08-06 14:40:46
4960
发布2021-08-06 14:40:46
举报

在之前一篇文章<<从lock_guard来说一说C++中常用的RAII>> 讲解了RAII, 其实一种常见的资源管理方式,减少了资源泄露的风险。同事和我说是不是就是智能指针, 准确来说RAII是一种思想,一般是利用栈上对象初始化进行资源的申请,在其生命周期结束的时候,自动调用其析构函数,对资源进行释放。比如std::string, std::lock_guard都属于RAII的一种实现,那么对于不同资源的管理我是否都要实现一个类似于std::lock_guard一样的实现,其实不然,这样写代码多么费劲。那么有没有类似于golang中defer的实现呢,在函数退出的时候,自动调用一些代码,比如实现资源释放?是可以的,我们一起来看一看吧。

golang中的defer

golang的一段代码如下,这段代码比较简单,就是打开文件,然后读取文件内容。我们需要关注的是defer这一样,这一段可以表明在函数退出的时候会调用fileObj.Close()去关闭文件。

代码语言:javascript
复制
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也可以指定一个函数去执行多行指令比如改成:

代码语言:javascript
复制
  defer func() {
    fmt.Println("Close file: ", strFileName)
    fileObj.Close()
  }()

这个功能如果在C++中使用也一定很棒,这样当打开文件之后,在C++中也用这个defer去关闭文件,就不用管后续有多少个return,多少个触发异常的地方,从而忘记关闭文件了。

C++中的defer实现

在C++ 11出来之后有了Lamdba之后实现defer更加便捷了。我们继续使用<<从lock_guard来说一说C++中常用的RAII>>中的例子来。回顾下述代码的问题。

  1. 在互斥区的代码有可能会有多处返回return, 在每个return处都加上mutex.unlock()代码感觉显得很不优雅。
  2. 互斥区代码也有可能抛出异常,而有些场景,你并不想在互斥区捕获异常,那么也就不会调用mutext.unlock()从而导致锁并没有释放。

在之前的文章我们描述过可以通过std::lock_guard来实现RAII,保证资源总是在函数退出时候释放锁。那么我们用defer如何来实现呢?

代码语言:javascript
复制
void function()
{
  mutex.lock();
  //互斥区执行代码;
  //...
  if (...)
  {
    //...
    mutex.unlock();
    return;
  }

  //...
  if (...)
  {
    //...
    mutex.unlock();
    return;
  }
  //...
  mutex.unlock();
}

我们先实现一个RAIIDefer的类, RAIIDefer的类接受的初始化为一个std::function<void()>参数.而我们通过lambda可以便捷引用函数内部的变量,这个lamda的对象,做为RAIIDefer的构造函数参数传入进去,并且在析构的时候调用,从而可以做到在函数退出的时候实现资源释放。

代码语言:javascript
复制
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 更像一点。这里宏我简单解释下,主要就是为了让其定义局部变量名字加上一个行号,从而避免名字冲突。

代码语言:javascript
复制
// 参考刘未鹏: 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
  // ......
}

参考

刘未鹏 《C++11(及现代C++风格)和快速迭代式开发》: http://mindhacks.cn/2012/08/27/modern-cpp-practices/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个程序员的修炼之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • golang中的defer
  • C++中的defer实现
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档