有什么理由不使用全局lambda吗?


89

我们有一个函数在其内部使用了非捕获的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?是否有理由不只是在各处使用lambda而不是“常规”全局函数?


我认为,如果不谨慎使用lambda,则捕获意外变量将导致许多错误
macroland

Answers:


61

不使用全局Lambda的一个重要原因是:这是不正常的。

C ++的常规函数​​语法自C时代诞生以来就已经存在。程序员数十年来一直知道语法的含义以及它们的工作方式(尽管公认,整个函数到指针的衰减有时甚至会咬住经验丰富的程序员)。如果具有“完全新手”以外的任何技能水平的C ++程序员都可以看到函数定义,则他们知道自己得到了什么。

全局λ完全是另一种野兽。它具有与常规功能不同的行为。Lambda是对象,而功能不是。它们具有一个类型,但是该类型不同于其功能的类型。依此类推。

因此,现在,您已经提高了与其他程序员交流的门槛。如果C ++程序员想要了解该函数的功能,则需要了解它们。是的,这是2019年,所以一个体面的C ++程序员应该对lambda的外观有所了解。但这仍然是一个更高的标准。

即使他们了解它,该程序员的脑海中的问题仍将是……为什么此代码的编写者以这种方式编写它?而且,如果您对这个问题没有一个好的答案(例如,由于您明确希望像Ranges定制点那样禁止过载),则应该使用通用机制。

在适当的情况下,将预期的解决方案优先于新颖的解决方案。使用最简单的方法来表达您的观点。


9
“自C以来的日子”之前,我的朋友
Lightness在Orbit比赛,

4
@LightnessRaces:我的主要观点是要表达C ++的函数语法自C以来就存在,所以很多人通过检查知道它的含义。
Nicol Bolas

2
同意所有这些答案,除了第一句话。“这很不正常”是一个模糊和含糊的说法。也许您的意思是“不常见且令人困惑”?
einpoklum

1
@einpoklum:C ++有很多东西可以被认为是“不常见且令人困惑的”,但在它们的领域内仍然是“正常的”(请参阅​​深度模板元编程)。我认为“正常”在这种情况下是完全有效的;在历史上和今天,这都不是C ++编程经验的“准则”之内的事情。
Nicol Bolas

@NicolBolas:但是从某种意义上讲,这是循环推理:OP在想我们是否应该采用一种罕见的做法。顾名思义,罕见的实践不是“规范”。但是nm。
einpoklum

53

我可以想到一些您希望避免将全局lambda替换为常规函数的替代方法的原因:

  • 常规功能可以重载;lambda不能(但是有模拟这种方法的技术)
  • 尽管它们类似于函数,但即使是这样的非捕获lambda也会占用内存(非捕获通常为1字节)。
    • 正如评论中指出的那样,现代编译器将根据as-if规则优化此存储。

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

  • 与lambda相比,类的限制更少,因此,这应该是您要做的第一件事
    • (公共/私有数据,重载,辅助方法等)
  • 如果lambda具有状态,则更难于推断何时变为全局状态。
    • 我们应该宁愿在尽可能小的范围内创建一个类的实例
  • 将非捕获的lambda转换为函数指针已经很困难,而且lambda不可能在捕获中指定任何内容。
    • 类为我们提供了一种简单的方法来创建函数指针,这也是许多程序员更喜欢的方法
  • 带有任何捕获的Lambda不能被默认构造(在C ++ 20中。以前在任何情况下都没有默认构造器)

还有一个指向lambda(甚至是非捕获对象)的隐式“ this”指针,这在调用lambda与调用函数时会导致一个额外的参数。
1201ProgramAlarm

@ 1201ProgramAlarm:很好。获取lambda的函数指针可能要困难得多(尽管不捕获的lambda可以衰减为常规函数指针)
AndyG

3
在另一方面,如果将其传递到某个地方而不是直接调用,则使用lambda而不是函数会促进内联。自然,可以将功能指针包装在std::integral_constant其中……
Deduplicator

@Deduplicator:“ 具有lambda而不是函数会促进内联 ”“需明确,这仅在” somewhere“是将其调用的函数作为任意可调用类型而不是函数指针的模板时才适用。
Nicol Bolas

2
@AndyG您会忘记以下假设规则:不需要lambda大小的程序(因为…为什么?!),也不需要创建指向该lambda的指针,则不需要(通常不需要)为它分配空间。
康拉德·鲁道夫


8

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

某种程度的复杂性问题需要至少相同复杂性的解决方案。但是,如果针对同一问题的解决方案不太复杂,那么实际上没有理由使用更复杂的解决方案。为什么要引入不需要的复杂性?

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


6

Lambda是匿名函数

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


1
这似乎是术语的争论,即。令人困惑的命名和含义。通过定义已命名的lambda不再是匿名(duh),可以很容易地反驳它。这里的问题不是lambda的使用方式,而是“匿名函数”用词不当,当lambda绑定到名称时不再准确。
康拉德·鲁道夫

@KonradRudolph:不仅仅是术语,这就是为什么它们首先被创建的原因。至少从历史上看,lambda是匿名函数,这就是为什么对它们进行命名令人困惑的原因,特别是在期望使用函数的情况下。
埃里克·杜米尼尔

1
没事 Lambda 通常是命名的(通常只是本地命名),对此没有任何混淆。
康拉德·鲁道夫

1
匿名函数不同意,它更像一个函子,但是无论如何,我看不到具有(命名)实例的问题,而不是强制将它们仅用作右值引用。(因为您的观点也应适用于本地范围)。
Jarod42 '19

5

是否有某种理由不将其保留为lambda?是否有理由不只是在各处使用lambda而不是“常规”全局函数?

我们过去使用函数代替全局函子,因此破坏了一致性和最小惊讶原则

主要区别在于:

  • 函数可以重载,而函子不能。
  • 函数可以通过ADL找到,而不是函子。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.