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

2024-04-14 01:12

本文主要是介绍超越传统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提供了更灵活和强大的函数计算方式,适用于大规模数据处理、机器学习和实时流处理等场景。
在这里插入图片描述

这篇关于超越传统Lambda函数:深入解析Out-of-line Lambdas的奇妙之处的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/901730

相关文章

Python19 lambda表达式

在 Python 中,lambda 表达式是一个小型匿名函数,通常用于实现简单、单行的函数。lambda 函数可以接受任意数量的参数,但只能有一个表达式。 基本语法: lambda arguments: expression 这里,arguments 是传递给 lambda 的参数,expression 是关于这些参数的表达式,它的计算结果就是 lambda 函数的返回值。 使用

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

解析 XML 和 INI

XML 1.TinyXML库 TinyXML是一个C++的XML解析库  使用介绍: https://www.cnblogs.com/mythou/archive/2011/11/27/2265169.html    使用的时候,只要把 tinyxml.h、tinystr.h、tinystr.cpp、tinyxml.cpp、tinyxmlerror.cpp、tinyxmlparser.

【操作系统】信号Signal超详解|捕捉函数

🔥博客主页: 我要成为C++领域大神🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 ​ 如何触发信号 信号是Linux下的经典技术,一般操作系统利用信号杀死违规进程,典型进程干预手段,信号除了杀死进程外也可以挂起进程 kill -l 查看系统支持的信号

java中查看函数运行时间和cpu运行时间

android开发调查性能问题中有一个现象,函数的运行时间远低于cpu执行时间,因为函数运行期间线程可能包含等待操作。native层可以查看实际的cpu执行时间和函数执行时间。在java中如何实现? 借助AI得到了答案 import java.lang.management.ManagementFactory;import java.lang.management.Threa

SQL Server中,isnull()函数以及null的用法

SQL Serve中的isnull()函数:          isnull(value1,value2)         1、value1与value2的数据类型必须一致。         2、如果value1的值不为null,结果返回value1。         3、如果value1为null,结果返回vaule2的值。vaule2是你设定的值。        如

tf.split()函数解析

API原型(TensorFlow 1.8.0): tf.split(     value,     num_or_size_splits,     axis=0,     num=None,     name='split' ) 这个函数是用来切割张量的。输入切割的张量和参数,返回切割的结果。  value传入的就是需要切割的张量。  这个函数有两种切割的方式: 以三个维度的张量为例,比如说一

C语言入门系列:探秘二级指针与多级指针的奇妙世界

文章目录 一,指针的回忆杀1,指针的概念2,指针的声明和赋值3,指针的使用3.1 直接给指针变量赋值3.2 通过*运算符读写指针指向的内存3.2.1 读3.2.2 写 二,二级指针详解1,定义2,示例说明3,二级指针与一级指针、普通变量的关系3.1,与一级指针的关系3.2,与普通变量的关系,示例说明 4,二级指针的常见用途5,二级指针扩展到多级指针 小结 C语言的学习之旅中,二级

PyTorch模型_trace实战:深入理解与应用

pytorch使用trace模型 1、使用trace生成torchscript模型2、使用trace的模型预测 1、使用trace生成torchscript模型 def save_trace(model, input, save_path):traced_script_model = torch.jit.trace(model, input)<

陀螺仪LSM6DSV16X与AI集成(8)----MotionFX库解析空间坐标

陀螺仪LSM6DSV16X与AI集成.8--MotionFX库解析空间坐标 概述视频教学样品申请源码下载开启CRC串口设置开启X-CUBE-MEMS1设置加速度和角速度量程速率选择设置FIFO速率设置FIFO时间戳批处理速率配置过滤链初始化定义MotionFX文件卡尔曼滤波算法主程序执行流程lsm6dsv16x_motion_fx_determin欧拉角简介演示 概述 本文将探讨