有啥理由不使用全局 lambda?

Posted

技术标签:

【中文标题】有啥理由不使用全局 lambda?【英文标题】:Any reason not to use global lambdas?有什么理由不使用全局 lambda? 【发布时间】:2020-04-08 05:56:06 【问题描述】:

我们有一个在自身内部使用非捕获 lambda 的函数,例如:

void foo() 
  auto bar = [](int a, int b) return a + b; 

  // code using bar(x,y) a bunch of times

现在其他地方需要 lambda 实现的功能,所以我将把 lambda 从 foo() 提升到全局/命名空间范围。我可以将其保留为 lambda,使其成为复制粘贴选项,或者将其更改为适当的函数:

auto bar = [](int a, int b) return a + b;  // option 1
int bar(int a, int b) return a + b;  // option 2

void foo() 
  // code using bar(x,y) a bunch of times

将其更改为适当的函数是微不足道的,但这让我想知道是否有某种原因将其保留为 lambda?有什么理由不只是在任何地方都使用 lambdas 而不是“常规”全局函数吗?

【问题讨论】:

我认为如果不仔细使用 lambda,捕获意外变量会导致许多错误 这个youtube.com/watch?v=Ft3zFk7VPrE的一些论据 【参考方案1】:

不使用全局 lambda 有一个非常重要的原因:因为它不正常。

自 C 时代以来,C++ 的常规函数​​语法就已经存在。几十年来,程序员已经知道所说的语法意味着什么以及它们是如何工作的(尽管不可否认,整个函数到指针的衰减问题有时甚至会困扰经验丰富的程序员)。如果 C++ 程序员在“完全新手”之外的任何技能水平看到函数定义,他们就会知道自己得到了什么。

全局 lambda 完全不同。它具有与常规功能不同的行为。 Lambda 是对象,而函数不是。它们有一个类型,但该类型不同于它们的函数类型。以此类推。

所以现在,您提高了与其他程序员交流的标准。如果 C++ 程序员要理解这个函数在做什么,他们需要理解 lambda。是的,现在是 2019 年,所以一个体面的 C++ 程序员应该知道 lambda 的样子。但它仍然是一个更高的标准。

即使他们理解它,那个程序员心中的问题也会是......为什么这段代码的作者会这样写?如果您对该问题没有很好的答案(例如,因为您明确想要禁止重载和 ADL,如 Ranges 自定义点),那么您应该使用通用机制。

在适当的情况下,更喜欢预期的解决方案而不是新颖的解决方案。使用最简单的方法来表达你的观点。

【讨论】:

“自 C 时代以来” 远在此之前我的朋友 @LightnessRaces:我的主要意思是表达 C++ 的函数语法从 C 开始就已经存在,所以很多人通过检查就知道它是什么。 同意所有这个答案,除了第一句话。 “这不正常”是一个含糊不清的陈述。也许您的意思是“它不常见且令人困惑”? @einpoklum:C++ 有很多东西可以被认为是“不常见和令人困惑的”,但在它们的领域内仍然很“正常”(参见深度模板元编程)。我认为“正常”在这种情况下是完全有效的;无论是历史上还是今天,这都不符合 C++ 编程经验的“规范”。 @einpoklum:“OP 想知道我们是否应该采用罕见的做法”不,OP 询问为什么我们不应该采用罕见的做法实践。答案是……这是一种罕见的做法。当涉及到像编程这样的社交活动时,做正常事情的最好理由是其他人知道它是什么。因为这很正常。规范是可以改变的,但前提是提出了一个具有足够和明显优势的替代方案,值得颠覆规范。【参考方案2】:

我可以想到一些您希望避免使用全局 lambda 作为常规函数的替代品的原因:

常规函数可以重载; lambdas 不能(但是有一些技术可以模拟这一点) 尽管它们是函数式的,但即使是这样的非捕获 lambda 也会占用内存(非捕获通常为 1 个字节)。 正如 cmets 中所指出的,现代编译器将根据 as-if 规则优化此存储

“为什么我不应该使用 lambdas 来替换有状态函子(类)?”

类的限制比 lambda 少,因此应该是您首先要达到的目标 (公共/私有数据、重载、辅助方法等) 如果 lambda 具有状态,则更难以推断它何时变为全局。 我们应该更倾向于在尽可能窄的范围内创建一个类的实例 将非捕获 lambda 转换为函数指针已经很困难,并且 lambda 不可能在其捕获中指定任何内容。 类为我们提供了一种创建函数指针的简单方法,并且它们也是许多程序员更习惯的方式 不能默认构造带有任何捕获的 Lambda(在 C++20 中。以前在任何情况下都没有默认构造函数)

【讨论】:

还有一个隐含的“this”指针指向一个 lambda(甚至是非捕获的),这会在调用 lambda 与调用函数时产生一个额外的参数。 @1201ProgramAlarm:这很好;获取 lambda 的函数指针可能要困难得多(尽管非捕获 lambda 可以衰减为常规函数指针) 另一方面,如果它被传递到某处而不是直接调用,使用 lambda 而不是函数会促进内联。自然,可以为此将函数指针打包在 std::integral_constant 中... @Deduplicator: "有一个 lambda 而不是一个函数会促进内联" 需要明确的是,这仅适用于“某处”是一个使用它的函数的模板调用作为任意可调用类型,而不是函数指针。 @AndyG 你忘记了 as-if 规则:不请求 lambda 大小的程序(因为......为什么?!),也没有创建指向它的指针,不需要(通常不)为其分配空间。【参考方案3】:

是否有任何理由不在各处使用 lambdas 而不是“常规”全局函数?

具有一定复杂性的问题需要至少具有相同复杂性的解决方案。但是,如果对于同一个问题有一个不太复杂的解决方案,那么就没有理由使用更复杂的解决方案了。为什么要引入你不需要的复杂性?

在 lambda 和函数之间,函数只是两者中不太复杂的实体。您不必证明不使用 lambda 是合理的。你必须证明使用一个。 lambda 表达式引入了一个闭包类型,它是一个未命名的类类型,具有所有常用的特殊成员函数、一个函数调用运算符,在这种情况下,还有一个到函数指针的隐式转换运算符,并创建该类型的对象。从 lambda 表达式复制初始化一个全局变量只是很多不仅仅是定义一个函数。它定义了一个具有六个隐式声明函数的类类型,定义了另外两个运算符函数,并创建了一个对象。编译器必须做更多的事情。如果你不需要 lambda 的任何特性,那么就不要使用 lambda...

【讨论】:

【参考方案4】:

询问后,我想到了一个不这样做的理由:由于这些是变量,它们很容易出现静态初始化顺序惨败(https://isocpp.org/wiki/faq/ctors#static-init-order),这可能会导致错误。

【讨论】:

你 could 让他们 constexpr lambdas...真正的重点是:只需使用一个函数。【参考方案5】:

Lambda 是 anonymous functions。

如果您使用的是命名 lambda,则意味着您基本上使用的是命名匿名函数。为了避免这种矛盾,你不妨使用一个函数。

【讨论】:

这似乎是术语的一个论点,即。混淆命名和含义。通过定义一个命名的 lambda 不再是匿名的 (duh) 可以很容易地反驳它。这里的问题不在于如何使用 lambda,而在于“匿名函数”是用词不当,当 lambda 绑定到名称时不再准确。 @KonradRudolph:这不仅仅是术语,这就是它们最初创建的原因。至少从历史上看,lambda 是匿名函数,这就是为什么给它们命名会让人困惑,尤其是在需要函数时。 不。 Lambda 是常规地命名的(通常是本地命名的),这没有什么令人困惑的地方。 不同意匿名函数,它更像是一个仿函数,但无论如何,我认为拥有(命名)实例而不是强制将它们仅用作右值引用。 (因为您的观点也应适用于本地范围)。【参考方案6】:

是否有理由不将其保留为 lambda?有什么理由不只是在任何地方都使用 lambdas 而不是“常规”全局函数吗?

我们过去使用函数而不是全局函子,因此它破坏了连贯性和最小惊讶原则

主要区别在于:

函数可以重载,而仿函数不能。 可以使用 ADL 找到函数,而不是函子。

【讨论】:

以上是关于有啥理由不使用全局 lambda?的主要内容,如果未能解决你的问题,请参考以下文章

有啥理由不放弃“var”吗?

有啥理由不使用“受保护”的属性吗?

有啥理由不使用 JSONP?

有啥理由不使用 Visual Studio 6 for C++? [关闭]

今天有啥理由不使用 <script defer> 吗?

使用 if(1 || !Foo()) 有啥理由吗?