C++ RAII实现golang的defer

在之前一篇文章<<从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>>中的例子来。回顾下述代码的问题。

  1. 在互斥区的代码有可能会有多处返回return, 在每个return处都加上mutex.unlock()代码感觉显得很不优雅。
  2. 互斥区代码也有可能抛出异常,而有些场景,你并不想在互斥区捕获异常,那么也就不会调用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
	// ......
}

参考

刘未鹏 《C++11(及现代C++风格)和快速迭代式开发》

猜你喜欢

转载自blog.csdn.net/CJF_iceKing/article/details/118244945