defer in C++/Objc
2021-05-14 06:27
标签:先进后出 强制 相同 for lease 函数调用 code int type 写过swift的同学应该都知道defer这个关键字,可以让我们在函数return之前执行指定的代码,这对于有多个提前return而忘记释放资源的函数来说,简直不要太方便了,然而对于swift的前辈Objective-C或C++来说,苹果并没有帮我们定义,因此本文总结一下如何在C++和Objective-C中实现defer。 正如导语所言, 虽然我们总是拿defer来帮函数做资源回收工作,但其实defer的作用范围是最近的作用域,假如我们将 此外当一个作用域定义了多个defer,那么退出作用域前其执行顺序就像栈一样,先进后出。 想要在Objective-C中完美实现defer,那么我们需要了解一下GNU C中的编译指令 cleanup指令可以说是非常符合我们当前的需求,该指令接受一个返回为空,参数个数是1个的函数指针作为其参数,在声明的作用域结束之前执行指定的函数。文字说明可能不够清楚,参考下列代码: 借助cleanup这个黑魔法,假如我们定义一个接受一个block指针参数的函数,其函数体就是直接执行该block参数,然后将该函数传给cleanup指令,那么就可以在作用域结束前执行指定的代码,正如以下代码所示: 虽然上面的代码已经可以基本实现我们的需求,但是假如每次使用都要敲上面这么长的声明变量语句,怕是很难记住,因此,参考Reactive Cocoa中神奇的@onExit宏,我们可以定义以下的宏: 其中ext_keywordify这个工具宏完全是为了让我们在onExit前添加@,显得更加特别而使用的,也为了更接近Reacive Cocoa而加的。通过onExit宏,上面那一长串的声明语句就可以简化为: @onExit到这里可以说已经非常接近defer的功能了,但依然还差一点,就是@onExit一个作用域只能声明一次,这是因为onExit宏中我们声明的变量名是ext_exitBlock_,这个固定的名字,所以相同作用域中不能有两个相同的名字的变量,否则编译就会出错。为了解决该问题,我们还需要借用_LINE_宏(_COUNTER_也可以),该宏会在编译后被替换为文件中所在的行号,所以假如我们将ext_exitBlock_这个变量名和行号混在一起,那么就不会有重复的变量名了,因此onExit宏最终的定义如下: 完整的定义如下: 以上代码都是ObjC的,但_attribute_编译指令是GNU通用的,所以在C++也可以用同样的方法,只是block参数替换为C++11的std::function,然后传入一个lambda函数就可以,这里就不赘述了。 defer的第二种实现可以借助局部变量的析构函数,因为局部变量会在调用堆栈返回前释放,这与defer的作用有点相似,故此我们稍加改造也可以实现defer的功能,如下列代码所示: 上述代码会在作用域结束时执行指定的lambda函数,而且同样的,我们让局部变量的名字后面加上行号,使得可以声明多个defer表达式。 在使用defer过程中,我们需要注意一点,假如我们在defer中修改函数的返回值,那么很抱歉,这是没有意义的事情,就好比下列代码: test函数中声明了@onExit block,并修改了返回值,但main函数调用完test函数后,res这个返回值依然是1。究其原因,就是因为return语句并不是原子语句,在test函数return时,执行的顺序是确定 defer in C++/Objc 标签:先进后出 强制 相同 for lease 函数调用 code int type 原文地址:https://www.cnblogs.com/8gedouy/p/13125993.html导语
defer的作用
defer
关键字可以帮我们在函数返回之前执行指定的代码,其中最常见的作用就是帮我们清理资源,防止某个地方提前return而导致内存泄露。defer的范围
defer
放入if
作用域中时,defer
就会在if
作用域结束前执行,而非函数return前,这需要在使用defer
多加小心,不然资源提前释放会导致野指针。defer with cleanup
__attribute__
__attribute__((attribute-list))
,该编译指令的括号里可以填非常多的指令,例如format
可以用来帮助printf
检查格式化字符串的参数类型对不对,又例如noreturn
用来告知编译器该函数并不是所有条件下都有返回值,编译时不需要输出warning,而我们现在需要用的是cleanup
指令。cleanup
// 指定一个cleanup方法,注意入参是所修饰变量的地址,类型要一样
// 对于指向objc对象的指针(id *),如果不强制声明__strong默认是__autoreleasing,造成类型不匹配
static void stringCleanUp(__strong NSString **string) {
NSLog(@"%@", *string);
}
// 在某个方法中:
{
__strong NSString *string __attribute__((cleanup(stringCleanUp))) = @"sunnyxx";
} // 当运行到这个作用域结束时,自动调用stringCleanUp
// void(^block)(void)的指针是void(^*block)(void)
static void blockCleanUp(__strong void(^*block)(void)) {
(*block)();
}
{
// 加了个`unused`的attribute用来消除`unused variable`的warning
__strong void(^block)(void) __attribute__((cleanup(blockCleanUp), unused)) = ^{
NSLog(@"I‘m dying...");
};
} // 这里输出"I‘m dying..."
#define ext_keywordify autoreleasepool {}
#define onExit ext_keywordify __strong ext_cleanupBlock_t ext_exitBlock_ __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^
{
@onExit {
NSLog(@"I‘m dying...");
};
} // 这里输出"I‘m dying..."
__LINE__
#define onExit ext_keywordify __strong ext_cleanupBlock_t tt_string_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^
#define ext_keywordify autoreleasepool {}
typedef void (^ext_cleanupBlock_t)(void);
void ext_executeCleanupBlock(__strong ext_cleanupBlock_t _Nonnull * _Nonnull block) {
(*block)();
}
#define onExit ext_keywordify __strong ext_cleanupBlock_t tt_string_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^
defer with dealloc
template
defer VS return
int test {
int __block result = 1;
@onExit {
result++;
};
return result;
}
void main() {
int res = test();
printf("test res: %d\n", res); //test res: 1
return;
}
返回值result = 1
-> 执行@onExit
-> 函数返回
,因此即使@onExit中修改了返回值,对于最终的函数返回值来说是没有改变的。
下一篇:Linux下安装 Java