原创文章,转载请注明出处。
前言 我在用Lambda的时候遇到了什么问题
最近写lambda函数的时候发现了一个问题。 捕获列表为引用捕获,引用出作用域后失效问题,情况是这样的:
我重写了UE4引擎Windows窗体退出的方法,方法参数传过来的是个引用, 然后我就直接将退出的代码写到了Lambda中。这个Lambda作为我的回调函数, 接下来会异步执行一些操作,异步完事后,调用这个Lambda,发现直接崩溃了。
上面说的可能复杂,其实就是类似下面的代码👇
因为我下面用的是引用捕获的方式,出了OnExit函数的作用域,其实window这个智能引用变量就失效了。再执行ExitSPFunc时候window就变成了一个悬空引用,失效了。进而引发数据异常或者程序崩溃。 那么解决方案就是你可以修改成 值捕获 的方式
void USPGameInstance::OnExit(const TSharedRef<SWindow>& window)
{
auto ExitSPFunc = [&]()
{
FSPDelegates::OnAsyncCloseProjectDelegate.Remove(m_Handle_ExitSPFunc);
FSlateApplication::Get().RequestDestroyWindow(window);
};
FSPDelegates::OnAsyncCloseProjectDelegate.Remove(m_Handle_ExitSPFunc);
m_Handle_ExitSPFunc = FSPDelegates::OnAsyncCloseProjectDelegate.AddLambda(ExitSPFunc);
AsyncCloseProject();
}
或者像下面的代码,Timer调用Lambda(引用捕获方式),同样会有引用失效问题,进而得不到你想要的结果。
FString MyTestStr = TEXT("MyTestStr");
auto MyLambda = [&MyTestStr]() {
UE_LOG(LogTemp, Warning, TEXT("%s"), *MyTestStr);
};
MyLambda();
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;
}
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;
}
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;
}
C++有哪些(类似)闭包的结构/语法
1>在类中重载()运算符 2>#include<functional<>>里面的std::bind回调函数 3>开始我们介绍的Lambda表达式
3>延展阅读
1>维基百科上对闭包的介绍 2>网上发现的一篇帖子,感觉挺靠谱的
最后,有问题请指正!谢谢。
谢谢,创作不易,大侠请留步… 动起可爱的双手,来个赞再走呗 <( ̄︶ ̄)>
|