笔者在翻译完 这篇文章 以及同系列的下一篇文章(尚未发布…)后,受到了 ESR 大神的鼓舞,遂决定在寒假学习一下 Go 语言。在学习 Go 语言的过程中,觉着这语言和之前学到的 C/C++ ,Scheme 相比有着无法比较的简洁感。笔者尤其喜欢
defer
这一关键字的设计。于是就在今天尝试使用 C/C++ 模拟了下defer
关键字。
---- 某咸鱼的碎碎念
关于 defer 的简介
defer
这一关键字可以说是 Go 语言的一大亮点。这一关键字通常用于资源的释放,会在函数返回之前进行调用。例如,当我们需要读取一个文件时,我们在 C/C++ 里面通常会如此操作:
void inm() {
FILE *fp;
fp = fopen("114514.txt")
if(!fp) {
// open file failed
}
// blah blah
if(ERROR OCCURED) {
// handle
fclose(fp);
return;
}
// blah blah
fclose(fp);
return;
}
有了 defer
之后,我们在 Go 语言中即可进行如下操作:
func inm() {
file,err := os.Open("114514.txt")
if err != nil {
// open file failed
}
defer file.Close()
// blah blah
return
}
这样我们就不必费心思考是否忘记关闭文件着一问题。只需处理我们真正需要处理的问题即可。同时代码的可读性也大幅提升。
defer 的行为
粗略地讲,当我们在使用 defer
这一关键字时,我们的语句就会就会被加入到表中。在函数 return
前,这个表里的函数按照后进先出的顺序执行。因此,对于如下示例:
func zafechi3() { // ザフェチ3
defer fmt.Print(" 24 ")
defer fmt.Print(" 歳 ")
defer fmt.Print(" 学生です ")
return
}
我们的输出就是 “ 学生です 歳 24 ”,而不是喜闻乐见的 “ 24 歳 学生です ”。
defer
的更多特性还有很多坑点都不在此说明。
C++ 实现
因为 defer
关键字的特性和 C++ 的类的析构函数类似,我们即可很容易的模拟出 C++ 版本的 defer
。如下是笔者的第一版 ·defer
:
template <typename Function>
struct doDefer {
Function f;
doDefer(Function f) : f(f) {}
~doDefer() { f(); }
}
template <typename Function>
doDefer<Function> deferer(Function F) {
return doDefer<Function>(f);
}
#define defer(expr) auto __defered = deferer([&](){expr;})
基本上实现了 defer
的功能,但是现在还无法在一个文件里多次调用 defer
,不给力啊老师!
在经过了半个小时的查找之后,笔者发现了 __COUNTER__
宏,这个宏就相当于是一个计数器,在编译时,__COUNTER__
会被替换为它在源码中已经出现的次数。这个特性使得其很适合拿来区分多个重复的变量,示例如下:
#define FUNC1(x,y) x##y
#define FUNC2(x,y) FUNC1(x,y)
#define FUNC(x) FUNC2(x,__COUNTER__)
int FUNC(a)
int FUNC(a)
int FUNC(A)
在编译时,那三个 int FUNC(a)
会被依次展开为 int a0
,int a1
,int a2
。利用这个特性,我们可以重写之前的 defer
:
#define DEFER_1(x, y) x##y
#define DEFER_2(x, y) DEFER_1(x, y)
#define DEFER_0(x) DEFER_2(x, __COUNTER__)
#define defer(expr) auto DEFER_0(_defered_option) = deferer([&](){expr;})
template <typename Function>
struct doDefer {
Function f;
doDefer(Function f) : f(f) {}
~doDefer() { f(); }
}
template <typename Function>
doDefer<Function> deferer(Function F) {
return doDefer<Function>(f);
}
这样,我们就能在 C++ 中愉快的使用 defer
辣~
与正版 defer 的区别
我们之前在说过,Go 语言的对于多个 defer
的处理是按照后进先出的顺序处理的。但是我们实现的 C++ 版本的 defer 显然不是如此,因为对于如下程序:
void inm() {
defer(std::cout << " 24 ";)
defer(std::cout <<" 歳 ";);
defer(std::cout <<" 学生です ";);
return;
}
我们的输出就是喜闻乐见的 “ 24 歳 学生です ”。不过,大致的功能还是相同的。
C 实现
在 C,至少是标准 C 里面,我们无法优雅地实现 defer
这一关键字,但是由于 gcc
以及 clang
的拓展的存在,使得我们还是可以在短短几行内实现这一操作。
我们使用的拓展如下:
其中 GCC 需要来自水果的补丁才能使用闭包功能,关于 Block 闭包,可以看这里。
有了这两个拓展,写出 C 语言版本的 defer
简直易如反掌:
static inline void deferer(void (^*expr)(void)) { (*expr)(); }
#define DEFER_1(x,y) x##y
#define DEFER_2(x, y) DEFER_1(x, y)
#define DEFER_0(x) DEFER_2(x, __COUNTER__)
#define defer __attribute__((cleanup(deferer))) void (^DEFER_0(_defered_option))(void) =
调用时候,只需:
defer ^{ <expr> };
emmmmm… 看着有点麻烦但也不是不能接受…
其他实现版本
写完着两份程序之后,笔者眉头一皱,觉得不简单。这个出现了近 10 年的关键字,肯定会有前辈实现过了。经过搜索,找出了如下几个,供读者欣赏:
// final_act allows you to ensure something gets run at the end of a scope
template <class F>
class final_act
{
public:
explicit final_act(F f) noexcept : f_(std::move(f)), invoke_(true) {}
final_act(final_act&& other) noexcept : f_(std::move(other.f_)), invoke_(other.invoke_)
{
other.invoke_ = false;
}
final_act(const final_act&) = delete;
final_act& operator=(const final_act&) = delete;
~final_act() noexcept
{
if (invoke_) f_();
}
private:
F f_;
bool invoke_;
};
// finally() - convenience function to generate a final_act
template <class F>
inline final_act<F> finally(const F& f) noexcept
{
return final_act<F>(f);
}
template <class F>
inline final_act<F> finally(F&& f) noexcept
{
return final_act<F>(std::forward<F>(f));
}
- pepper_chico
using defer = shared_ptr<void>;