IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++ 从Lambda的使用到对C++闭包语法的理解 -> 正文阅读

[C++知识库]C++ 从Lambda的使用到对C++闭包语法的理解

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

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

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

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

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

因为我下面用的是引用捕获的方式,出了OnExit函数的作用域,其实window这个智能引用变量就失效了。再执行ExitSPFunc时候window就变成了一个悬空引用,失效了。进而引发数据异常或者程序崩溃。 那么解决方案就是你可以修改成 值捕获 的方式

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>网上发现的一篇帖子,感觉挺靠谱的

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

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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-30 11:49:20  更:2021-08-30 11:51:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/18 6:42:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码