本文记录用C++ 来实现 Go语言中与defer类似的功能, 与RAII配合使用
代码地址:https://github.com/pengguoqing/samples_code
在Go 语言里面有一个 defer 声明, 它的作用是将函数调用保存在列表中, 函数返回时依次调用列表中的函数。
之前实现简易版的智能指针文章中指出, 智能指针内部就是利用的 RAII特点, 将对象的声明周期使用栈来管理。 因此可以借鉴 Go语言中的 defer逻辑, 然后结合RAII的特点来实现一个 C++ 版本的 defer, 依次来实现在当前作用域内必须要调用的函数。
关于离开作用域时某些函数必须调用的一个经典例子就是文件的关闭, 如下:
FILE *file = fopen("readme.ext", "rb");
if (file == NULL)
{
return;
}
if (caseOne)
{
// dosomething
fclose(file); // 必须手动关闭文件
return;
}
//caseTwo
//dosomething
fclose(file); // 必须手动关闭文件
return;
这种情况下需要显示的在多个 return 的之前调用 fclose(file)关闭文件, 一方面维护的时候可能会忘记关闭文件, 而且代码看起来也不够简洁。熟悉 RAII 技术后, 常用的方式是实现一个相关 scope 类封装, 在文件开发成功后将 FILE 句柄传入构造函数,在离开作用域时利用栈资源销毁调用 scope 类的析构函数 关闭文件。这当前时一种解决方案, 但是每当遇到一个这种场景时就需要手动封装一个相关的 scope 类。所以需要一个 类似 defer的东西方便使用。
①编译器类型自动推导; ②Lambda表达式(仿函数); ③RAII; ④转发引用
对函数调用只需要一次, 所以需要类似 unique_ptr的方式, 管理仿函数对象的类只能被移动, 不能被拷贝和赋值,声明如下:
public:
inline CXDeferImpl(const Functor& functor);
inline CXDeferImpl(Functor&& functor);
inline CXDeferImpl(CXDeferImpl&& another);
inline ~CXDeferImpl();
private:
//不允许拷贝, 赋值
CXDeferImpl(const CXDeferImpl& another) = delete;
CXDeferImpl operator=(const CXDeferImpl& another) = delete;
CXDeferImpl operator=(CXDeferImpl&& another) = delete;
类成员只需要一个仿函数对象和一个是否有效的标志:
private:
Functor m_func;
bool m_valid;
以宏的方式对外提供调用, 利用 Lambda表达式封装需要调用的函数,毕竟它的内部就是一个实现仿函数的类, 再让编译器自动推导 Lambda 类型来构造一个 CXDeferImpl实例, 每次 defer() 声明都创建一个对象 CXDeferImpl。
#define CONCAT_(a, b, c) a##b##c
#define CONCAT(a, b, c) CONCAT_(a, b, c)
#define defer(x) \
auto CONCAT(defer_, __LINE__, __COUNTER__) = MakeDeferIns([&]{x;})
当然是是用万能并且经典的 “Hello world!” 了, 哈哈哈。测试其离开作用域时能不能完整的输出的 “Hello world!”, 代码如下:
void TestDefer()
{
defer(cout<<"world!"<<endl);
cout<<"Hello ";
}
void TestDefer2()
{
{
defer(TestDefer());
cout<<"Test defer in scope"<<endl;
}
cout << "Test defer are ok?" << endl;
defer(cout<<"TestDefer2"<<endl);
}
void TestDefer3(int a)
{
{
defer(cout << "test param func " << a << endl);
}
defer(cout << "TestDefer3" << endl);
}
int main()
{
TestDefer2();
TestDefer3(100);
return 0;
}