C++ 从Lambda的使用到对C++闭包语法的理解/Lambda的坑

原创文章,转载请注明出处。

前言 我在用Lambda的时候遇到了什么问题

最近写lambda函数的时候发现了一个问题。
捕获列表为引用捕获,引用出作用域后失效问题,情况是这样的:

我重写了UE4引擎Windows窗体退出的方法,方法参数传过来的是个引用,
然后我就直接将退出的代码写到了Lambda中。这个Lambda作为我的回调函数,
接下来会异步执行一些操作,异步完事后,调用这个Lambda,发现直接崩溃了。

上面说的可能复杂,其实就是类似下面的代码👇

因为我下面用的是引用捕获的方式,出了OnExit函数的作用域,其实window这个智能引用变量就失效了。再执行ExitSPFunc时候window就变成了一个悬空引用,失效了。进而引发数据异常或者程序崩溃。 那么解决方案就是你可以将window变量存下来,或者是值拷贝,比如存到类里面,在lambda内部的时候调用你存下来的变量。或者是只要有延迟调用的Lambda都不要在用Lambda。
UE4这块只要是你用Lambda,如果是延迟调用的话,那么你就得注意。

void USPGameInstance::OnExit(const TSharedRef<SWindow>& window)
{
	//注意这里是引用捕获捕获
	auto ExitSPFunc = [&]()
	{
		//异步执行到这之后window这个引用就已经失效了。再接着调用下面的RequestDestroyWindow方法后,程序就崩溃了。
		FSPDelegates::OnAsyncCloseProjectDelegate.Remove(m_Handle_ExitSPFunc);
		FSlateApplication::Get().RequestDestroyWindow(window);
	};
	FSPDelegates::OnAsyncCloseProjectDelegate.Remove(m_Handle_ExitSPFunc);
	m_Handle_ExitSPFunc = FSPDelegates::OnAsyncCloseProjectDelegate.AddLambda(ExitSPFunc);

	//异步调用关闭, 当异步结束会发送OnAsyncCloseProjectDelegate的广播, 进而上面收到
	AsyncCloseProject();
}

或者像下面的代码,Timer调用Lambda(引用捕获方式),同样会有引用失效问题,进而得不到你想要的结果。

//定义一个字符串
FString MyTestStr = TEXT("MyTestStr");

//定义Lambda表达式
auto MyLambda = [&MyTestStr]() {
	UE_LOG(LogTemp, Warning, TEXT("%s"), *MyTestStr);
};

//顺序调用Lambda是没有问题的
MyLambda();

//定时器中调用Lambda表达式 定时器再调用MyLambda函数时候你会发现打印的都是null, 因为引用已经失效
FTimerDelegate MyTimerDelegate;
MyTimerDelegate.BindLambda(MyLambda);
FTimerHandle MyTimerHandle;
GetWorld()->GetTimerManager().SetTimer(MyTimerHandle, MyTimerDelegate, 1.f, true);

所以我们得到一个结论,就是Lambda(或者叫闭包)虽然是个语法糖,很好用,但是用的时候要注意捕获形式为引用捕获时,引用可能失效问题。程序员要自己把握好这个度。

1>Lambda使用

Lambda表达式的一个 好处 就是在需要的时候临时创建函数,便捷。
再有就是我们也可以做回调函数等。
一种语法糖。
其实在外面写一个函数是等价的,Lambda更方便。

1.1>Lambda基础语法

此处就不过于赘述基础使用了,可跳转至 点我进行查看C++11Lambda基础语法 进行查看。

1.2>Lamda(闭包语法)使用注意事项,引入闭包概念

先提个问题,你有思考过Lambda底层是怎么实现的吗?

在这里引出一个闭包的概念,首先Lambda就是C++11引入的一种闭包语法。

C++闭包里面存储了其闭包代码内引用的外部变量的 拷贝 或者 引用
如果闭包内部存储的是外部变量的引用,当执行到该闭包逻辑时,
如果外部引用失效了(比如出了作用域后),那么就会引发不确定的问题,比如数据异常,
或者程序崩溃掉。
C++闭包里面只是在用这些变量,但是它不会管变量的生命周期。
所以说我们C++程序员在进行闭包逻辑书写的时候,要思考 捕获方式为引用的变量是否会出现失效问题。

那么什么是闭包?C++里面有哪些闭包语法呢?

2>闭包介绍

闭包啥意思?什么是闭包?

以下内容摘自 维基百科,在维基百科内容基础上添加了一些内容(蓝色字部分,注意网上的一些Blogs说词都不准确)。

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体 这里注意官方准确说法是结构体(网上很多说贴子说的是类),它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,或者理解成运行时被确定/捕获,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定(就比如我们C++里面的Lambda)。

回答上面提到的问题:你有思考过Lambda底层是怎么实现的吗?

这里提到了闭包在实现上是一个结构体,正好回应了上面的问题 **你有思考过Lambda底层是怎么实现的吗?
这个结构体内部有具体实现,比如
按值捕获结构体内就是存的一份拷贝的变量,按引用捕获存放的就是指针。
最终我感觉可能是再通过 结构体内重载() 来进行实现。

**

闭包和匿名函数经常被用作同义词。但严格来说,匿名函数就是字面意义上没有被赋予名称的函数,而闭包则实际上是一个函数的实例,也就是说它是存在于内存里的某个结构体。如果从实现上来看的话,匿名函数如果没有捕捉自由变量,那么它其实可以被实现为一个函数指针,或者直接内联到调用点,如果它捕捉了自由变量那么它将是一个闭包;而闭包则意味着同时包括函数指针和环境两个关键元素。在编译优化当中,没有捕捉自由变量的闭包可以被优化成普通函数,这样就无需分配闭包结构体,这种编译技巧被称为函数跃升

闭包的用途

因为闭包只有在被调用时才执行操作,或者理解成运行时被确定/捕获(暂且不论用于生成这个闭包对象本身的开销,比如按值捕获意味着执行复制构造函数),即“惰性求值”,所以它可以被用来定义控制结构。

闭包的实现

典型实现方式是定义一个特殊的数据结构,保存了函数地址指针与闭包创建时的函数的词法环境表示(那些非局部变量的绑定)。使用函数调用栈的语言实现闭包比较困难,因而这也说明了为什么大多数实现闭包的语言是基于垃圾收集机制——当然,不使用垃圾收集也可以做到。

闭包的实现与函数对象很相似。

通过将自由变量放进参数表、并扩大函数名字的作用域,可以把一个闭包 / 匿名 / 内部函数变成一个普通的函数,这叫做“Lambda 提升”。例:

void G(void){
    const std::wstring wstr=L"Hello, world!";
    std::function<wchar_t(size_t)> fn=[&wstr](size_t ui)->wchar_t{
        return wstr[ui%wstr.length()];
    };
    std::wcout<<fn(3)<<std::endl;//'l'
}
//那么 fn 是一个闭包,指向那个匿名函数。
//这里 wstr 是自由变量,首先将其放入参数表:
void G(void){
    const std::wstring wstr=L"Hello, world!";
    std::function<wchar_t(size_t, const std::wstring &)> fn=[](size_t ui, const std::wstring &wstr)->wchar_t{
        return wstr[ui%wstr.length()];
    };
    std::wcout<<fn(3, wstr)<<std::endl;//'l'
}
//现在 fn 中没有自由变量了。把这个匿名函数取个名之后放到全局命名空间里:
wchar_t fn(size_t ui, const std::wstring &wstr){
    return wstr[ui%wstr.length()];
}
void G(void){
    const std::wstring wstr=L"Hello, world!";
    std::wcout<<fn(3, wstr)<<std::endl;//'l'
}
//这就把 fn“提升”成了一个普通的函数。

C++有哪些(类似)闭包的结构/语法

1>在类中重载()运算符
2>#include<functional<>>里面的std::bind回调函数
3>开始我们介绍的Lambda表达式

3>延展阅读

1>维基百科上对闭包的介绍
2>网上发现的一篇帖子,感觉挺靠谱的

最后,有问题请指正!谢谢。

谢谢,创作不易,大侠请留步… 动起可爱的双手,来个赞再走呗 <( ̄︶ ̄)>

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WhiteTian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值