超越传统Lambda函数:深入解析Out-of-line Lambdas的奇妙之处

超越传统函数:深入解析线外 Lambda函数 的奇妙之处

  • 一、背景
  • 二、lambda 的捕获
  • 三、可能出现的警告
  • 四、lambda的广义捕获
  • 五、为每种情况进行重载
  • 六、总结

一、背景

Out-of-line Lambdas翻译过来就是“线外Lambda函数”或“离线Lambda函数”。Lambda 是使代码更具表现力的好工具,Out-of-line Lambdas是指在C++编程语言中,将Lambda函数的定义和实现分离的一种技术。Lambda函数是一种能够在代码中方便地定义匿名函数的特性,它可以在需要函数对象的地方直接使用,并且可以捕获周围作用域的变量。

通常情况下,Lambda函数是内联定义的,也就是在使用它的地方直接定义和使用,这样可以方便地将函数逻辑放在需要的地方,提高代码的可读性和可维护性。但是,有时候Lambda函数的实现逻辑较为复杂,或者需要在多个地方重复使用,这时就可以使用Out-of-line Lambdas来将Lambda函数的定义和实现分开。

使用Out-of-line Lambdas,可以将Lambda函数的定义放在一个地方,而将实现放在另一个地方,通过函数指针或函数对象的方式进行调用。这样做的好处是可以将复杂的函数逻辑从主要代码中分离出来,使主要代码更加简洁和易读。同时,Out-of-line Lambdas也可以在多个地方重复使用,提高代码的重用性。

如下代码:

auto const product = getProduct();

std::vector<Box> goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes),
    [product](Box const& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    });

我们不希望在调用代码的中间看到这种细节。这就提出了一个问题:什么时候应该使用动态临时 lambda,以及何时应该创建Out-of-line Lambda函数来减轻调用点的负担。

auto const product = getProduct();

std::vector<Box> goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), resists(product));

这个解决方案看起来更好,因为 lambda 的主体处于比周围代码更低的抽象级别。
不过,这并不意味着应该避免使用 lambda。 resists可以使用 Out-of-line lambda 函数实现:

auto resists(Product const& product)
{
    return [product](Box const& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    };
}

如果以前没有见过这种技术,请花点时间阅读上面的代码:它是一个函数(resist),它获取上下文(product)并返回一个捕获product的函数(未命名的lambda)。

返回类型是 lambda 的类型,由于它是由编译器确定的,并且我们程序员不知道,因此这里使用一个方便的auto作为函数的返回类型。

但是上面的代码(至少)有一个问题,接着往下看。

二、lambda 的捕获

上面代码中的一个问题是 lambda 通过复制捕获。但没有必要在这里复制,这个lambda在语句末尾被std::copy_if破坏,并且product在此期间保持活动状态。lambda也可以通过引用来获取product

auto resists(Product const& product)
{
    return [&product](Box const& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    };
}

这等同于通过复制捕获的先前版本,只是此代码不创建副本。

这看起来都很好,只是如果稍微改变一下调用的地方,这段代码就会中断。调用点如下所示:

auto const product = getProduct();

std::vector<Box> goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), resists(product));

如果给lambda起一个名字,同时去掉product中介对象,会怎么样?

std::vector<Box> goodBoxes;
auto const isAGoodBox = resists(getProduct());
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), isAGoodBox);

结果是,变成了未定义的行为。事实上,getProduct返回的Prouct现在是一个临时对象,在其语句结束时被销毁。当std::copy_if调用isGoodBox时,它会调用这个已经销毁的product

reslists中通过引用捕获使代码变得脆弱。

三、可能出现的警告

在大多数情况下,这段代码都是在没有任何警告的情况下编译的。编译器发出警告的唯一情况是:

  • 使用gcc;
  • 在优化水平为-O1的情况下;
  • 并且当使用对构造函数的直接调用构建临时对象时(Product{1.2})。
auto const isAGoodBox = resists(Product{1.2});
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), isAGoodBox);

在这个示例中,警告是这样的:

warning: '<anonymous>.Product::density_' is used uninitialized in this function [-Wuninitialized]
     double getDensity() const { return density_; }

但在其他配置中(-O0-O2-O3,使用中介函数getProduct(),或使用clang编译)都没有产生警告。

四、lambda的广义捕获

可以使用广义lambda捕获将临时Product移动到我们的lambda中。C++14为lambdas带来了一个新特性:广义lambda捕获。它允许在lambda的捕获中执行一些自定义代码:

[context = f()](MyType const& myParameter){ /* body of the lambda */ }

让我们利用广义lambda捕获来移动临时对象:

auto resists(Product&& product)
{
    return [product = std::move(product)](const Box& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    };
}

通过对代码的这种修改,在临时product(从中移出)被销毁后,lambda将使用自己的prduct继续其生命周期。不再有未定义的行为。

现在,不能再使用之前的版本了:

auto const product = getProduct();

std::vector<Box> goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), resists(product));

事实上,product在这里是一个左值,因此不能绑定到右值引用。为了强调这一点,编译器毫不客气地拒绝了这段代码:

error: cannot bind rvalue reference of type 'Product&&' to lvalue of type 'const Product'

五、为每种情况进行重载

一种解决方案是对resist进行两次重载:一次采用左值引用,另一次采用右值引用。

auto resists(Product const& product)
{
    return [&product](const Box& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    };
}

auto resists(Product&& product)
{
    return [product = std::move(product)](const Box& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    };
}

这会造成代码重复,这是我们应该避免的技术代码重复的情况之一。解决此问题的一种方法是将业务代码分解为由其他两个调用的第三个函数:

bool resists(Box const& box, Product const& product)
{
    const double volume = box.getVolume();
    const double weight = volume * product.getDensity();
    const double sidesSurface = box.getSidesSurface();
    const double pressure = weight / sidesSurface;
    const double maxPressure = box.getMaterial().getMaxPressure();
    return pressure <= maxPressure;
}

auto resists(Product const& product)
{
    return [&product](const Box& box)
    {
        return resists(box, product);
    };
}

auto resists(Product&& product)
{
    return [product = std::move(product)](const Box& box)
    {
        return resists(box, product);
    };
}

通用解决方案:该解决方案的优点是,它通过隐藏较低级别的详细信息,允许在调用站点上使用表达型代码,并且它对左值和右值都能正确工作。一个缺点是它创建了lambda的多个重载的样板。

六、总结

如果Out-of-line Lambdas利大于弊,减轻缺点会很有趣。一种方法是创建一个通用组件来封装多个重载的机制。使用这个通用组件,而不是每次都编写样板文件。

本文全面介绍了Out-of-line Lambdas在函数计算领域的奇妙之处。可以对传统Lambda函数以外的Out-of-line Lambdas有更深入的了解。Out-of-line Lambdas提供了更灵活和强大的函数计算方式,适用于大规模数据处理、机器学习和实时流处理等场景。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/541260.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

应用实战|从头开始开发记账本2:基于模板快速开始

上期视频我们创建好了BaaS服务的后端应用。从这期视频开始&#xff0c;我们将从头开发一个互联网记账本应用。本期视频我们介绍一下如何使用模板快速开启我们的应用开发之旅。 应用实战&#xff5c;从头开始开发记账本2&#xff1a;基于模板快速开始 相关代码 本期视频我们介绍…

浅析分布式业务一致性方案

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章&#xff0c;主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等&#xff0c;同时欢迎大家加我微信「java_front」一起交流学习 1 场景分析 现在有一种业务场景&#xff1a;A作为消息发送方&#xff0c;处理业…

Project Euler_Problem 178_Step Numbers_动态规划

原题目&#xff1a; 解题思路&#xff1a;动态规划 代码&#xff1a; ll R[50][11][2048];void solve() {ll i, j,k,x,y,z,p,q,u,v;N 40, NN 1024;//N 20;double a, b, c,d;for (i 0; i < 9; i) {R[1][i][1 << i] 1;}for (i 2; i < N; i) {for (j 0; j &…

三小时零基础入门微信扫码点餐小程序 手把手带你开发一款云开发版点餐软件,店铺地图导航,外卖小程序,用户端和后厨端都有

从今天开始带领大家实现一款云开发版的点餐小程序 视频讲解&#xff1a;《云开发后台微信扫码点餐小程序cms网页管理后台》 技术选型 1&#xff0c;前端 微信小程序原生框架cssJavaScript 2&#xff0c;管理后台 云开发Cms内容管理系统web网页 3&#xff0c;数据后台 小…

推荐几款常用Web自动化测试神器!

1、介绍 Web自动化测试在保证质量、提升效率、软件开发加速迭代上起到关键作用&#xff0c;它已经成为现代软件测试中不可或缺的一部分&#xff0c;今天给大家介绍推荐几款常用的Web自动化测试工具。 2、常用测试工具 常用的Web自动化测试工具包括&#xff1a; Selenium&am…

Vue.js npm错误:transpileDependencies.map不是一个函数

这个错误通常是由于npm版本不兼容导致的。在旧版本的npm中&#xff0c;transpileDependencies是一个字符串数组&#xff0c;我们可以直接配置需要编译的依赖库。而在较新版本的npm中&#xff0c;transpileDependencies被改成了一个对象&#xff0c;并且需要使用map()方法来处理…

有限差分法求解一维、二维波动方程

差分格式方法是数值计算方法中微分以及偏微分导数的一种离散化方法。具体来说&#xff0c;它使用相邻两个或者多个数值点的差分来取代偏微分方程中的导数或偏导数。选择差分格式是离散化偏微分方程的第一步&#xff0c;通过这种离散化&#xff0c;我们可以将连续空间区域上的问…

【UE 委托】如何利用函数指针理解委托的基本原理

目录 0 引言1 函数指针模拟多播委托 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;UE虚幻引擎专栏&#x1f4a5; 标题&#xff1a;【UE 委托】如何利用函数指针理解委托的基本原理❣️ 寄语&#xff1a;书到用时方恨少&#xff0c;事非经过不知难…

适用于 Windows 的 10 个免费数据恢复工具集合

有时&#xff0c;我们都会在个人计算机上意外删除一些重要文件或数据。我们无需再担心此类问题&#xff0c;因为我们可以借助互联网上提供的免费数据恢复工具来恢复宝贵的数据和图像。 互联网上有许多免费的数据恢复工具&#xff0c;从一长串工具中&#xff0c;我们列出了最好…

阿里云优惠口令2024最新

2024年阿里云域名优惠口令&#xff0c;com域名续费优惠口令“com批量注册更享优惠”&#xff0c;cn域名续费优惠口令“cn注册多个价格更优”&#xff0c;cn域名注册优惠口令“互联网上的中国标识”&#xff0c;阿里云优惠口令是域名专属的优惠码&#xff0c;可用于域名注册、续…

【软考中级】软件设计师考点分布

文章目录 软考官网资格设置软考报考流程 【软件设计师】考点分布选择题考点分布案例题考点分布 软考官网 中国计算机技术职业资格网&#xff1a;https://www.ruankao.org.cn/ 官网报名平台&#xff1a;https://bm.ruankao.org.cn/sign/welcome 资格设置 计算机软件计算机网…

RNN知识体系构筑:详尽阐述其理论基础、技术架构及其在处理序列数据挑战中的创新应用

一、为什么需要RNN 尽管神经网络被视为一种强大且理论上能够近似任何连续函数的模型&#xff0c;尤其当训练数据充足时&#xff0c;它们能够在输入空间中的某个点( x )映射到输出空间的特定值( y )&#xff0c;然而&#xff0c;这并不能完全解释为何在众多应用场景中&#xff…

数据结构排序篇上

排序的概念及其运用 排序的概念 排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性 &#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&…

震惊!借助Coze白嫖GPT4-128k解决方案

震惊!某大佬借助Coze白嫖GPT4-128k解决方案 前言 此文章介绍如何免费使用GPT-4高级模型并拓展API功能 最近的 Coze 在国内开放了&#xff0c;可以免费使用大模型。但是和国外的有点区别&#xff0c;国外版本使用的chatgpt4&#xff0c;国内版本使用的是语雀大模型。 Coze是一…

功能测试_订购单检查_判定表

画判定表的步骤&#xff1a; 列出条件 列出动作

964: 数细胞

样例&#xff1a; 解法&#xff1a; 1.遍历矩阵 2.判断矩阵[i][j]&#xff0c;若是未标记细胞则遍历相邻所有未标记细胞并标记&#xff0c;且计数 实现&#xff1a;遍历相邻所有未标记细胞 以DFS实现&#xff1a; function dfs(当前状态) {if (终止条件) {}vis[标记当前状…

设计模式——外观(门面)模式10

外观模式&#xff1a;能为系统框架或其他复杂业务流程封装提供一个简单的接口。 例如抽奖过程中 设计模式&#xff0c;一定要敲代码理解 调用1&#xff08;抽奖系统&#xff09; /*** author ggbond* date 2024年04月08日 10:34*/ public class Lottery {public String getId…

x86处理器工作原理

对于电脑&#xff0c;大家可能司空见惯。但有没有想过它的处理器是如何工作的呢&#xff1f;下面和大家一起学习它的工作原理。 一. 最早的处理器 1.1 技术的发展 1947年&#xff0c;美国贝尔实验室的肖克利和同事们一起发明了晶体管。 1958年&#xff0c;美国人杰克基尔比发…

SpringBoot 集成H2数据库,启动执行sql, 中文乱码

目录 H2数据库介绍 SpringBoot版本&#xff1a;SpringBoot 2.1.12.RELEASE 快速集成H2&#xff0c;maven依赖 快速集成H2&#xff0c;数据源及关键参数配置 spring.datasource.schema参数&#xff08;建表SQL脚本&#xff09; spring.datasource.data参数&#xff08;更新、…