【问题标题】:What is the lifetime of the target of pointer-to-function pointing to a lambda?指向 lambda 的函数指针目标的生命周期是多少?
【发布时间】:2016-05-08 02:30:24
【问题描述】:

对不起,这是一个冗长的问题,但让我分解一下:

C++ 标准是否保证:

void (*Ptr)(void) = [] {};
return Ptr;

仍将被定义行为?

我知道,对于闭包,它将被定义,因为该闭包对象是按值移动/复制的;但是,虽然我知道“常规”函数具有无限/无生命周期,但 Ptr 的目标是否相同?还是每次实例化 lambda 时都会销毁并重新创建它?

我关心的原因是,如果不是,我不能使用 lambdas 作为回调。我想知道。

【问题讨论】:

  • 有趣的问题。由于指针将指向某个 static "invoker" 成员,因此静态成员别无选择,只能使用一些虚拟对象来调用 lambda 的 operator()(因为后者是非静态的) .该虚拟对象应该是“调用者”的本地对象,或者是静态的,即它应该在上述上下文中正常工作。但我没有立即在语言规范中看到这种保证。语言规范甚至没有假设该虚拟对象的存在。
  • @ant 为什么?它只需要运行相同的代码。为什么需要在operator() 中? operator()可以通过调用operator void(*)()()返回的静态函数来实现
  • 我希望所有“啰嗦”的问题都这么短
  • 我假设你的意思是问:如果调用代码然后通过返回的指针进行函数调用,它会被定义为行为吗?
  • @M.M 所有示例都是非规范性的,无论是否在注释中。

标签: c++ lambda language-lawyer


【解决方案1】:

对象有生命周期; 函数没有。函数不生不死;它们一直存在。因此,函数不能“超出范围”,先前有效的函数指针指向的函数也不能消失。不管它们来自哪里,函数指针总是有效的。

现在,这会忽略动态加载等,但这是超标准行为。

从 lambda 返回的函数指针是函数指针。这不是特别或魔法。因此,它的行为与任何其他函数指针没有什么不同。


是否有可能转换为 void (*)() 的结果指向调用绑定到某个对象的成员函数的东西?

这是一个复杂得多的问题。 C++17 标准似乎没有详细说明的一种,但 C++20 对它的规定要清楚得多。

C++17 standard only says:

函数的地址,当被调用时,与调用闭包类型的函数调用运算符具有相同的效果。

“相同效果”的确切含义是问题。有人可能会争辩说,“相同的效果”意味着执行函数调用运算符会执行的操作,执行相同的语句序列。人们也可以争辩说“相同的效果”意味着调用闭包对象本身。

后一种情况听起来可能难以实现,但请记住,可以使用编译器魔法。闭包可以返回一个特定于实例的函数指针,由闭包根据请求分配。或者类似的。

C++20 标准更加清晰,部分原因在于可以默认构造无捕获 lambda:

函数 F 的地址,在调用该函数时,其效果与在默认构造的闭包类型实例上调用闭包类型的函数调用运算符具有相同的效果。

因此,从 C++20 开始,标准清楚地表明调用函数指针与创建它的 lambda 对象的存在无关。

【讨论】:

  • 成员函数如何适应这个?
  • @M.M:指向成员的指针是一种不同的东西(并且是题外话),但它们仍然遵循相同的想法。 “成员”不是对象。因此,它们没有生命周期。成员指针始终有效。但是,您仍然需要一个对象来调用它们;该对象可能有效也可能无效。
  • 成员函数本质上是带有额外隐藏参数(this)的普通函数。所以一旦你加载了一个程序,所有的成员函数都在它们的位置,但是还没有对象
  • @M.M:更新了帖子。
  • @NicolBolas 很酷。看起来问题 1937 也注意到这可以更好地指定。
【解决方案2】:

lambda 函数只是真正函数或函子的语法糖(即具有operator() 和一些成员的对象,通常在构造时定义)。所以,这样的函数或方法是在编译过程中静态定义的。

虽然标准可能没有完全指定确切的实现方式,正如@NicolBolas 指出的那样,但实际实现似乎遵循严格的准则:没有上下文的 lambda 可以转换为普通函数指针,不创建中间对象,既不是在 lambda 定义的地方,也不是在调用的地方。我刚刚(再次)检查了它的 gcc 和 clang,我几乎可以肯定 MSVC 也会这样做。

注意:其余部分是关于带有上下文的 lambda,尽管对我来说这似乎更有趣和实用,但该问题明确涉及无上下文的 lambda。

上下文存储在 lambda 中(想象一个带有一些有意义的参数的仿函数对象,在对象构造时接收)。因此,如果您将一些引用或指针传递给上下文,这些引用和指针(例如this)不会自动延长其对应实体的生命周期。这就是为什么当您将 lambda 保存在未定义的范围内时,您应该格外小心。

resolved issue 中查看与 lambda 及其上下文范围定义相关的问题示例。检查修复以了解如何使存储的 lambdas 与上下文安全。

【讨论】:

  • “具有空上下文的 lambda 始终转换为匿名静态函数” 什么?编号auto f=[]{}; static_assert(std::is_same<decltype(f),void(*)()>{},"nope");
  • 好吧,你是对的。但是仍然可以将这样的 lambda 分配给指向函数的相应指针,就像在主题启动器的示例中一样。
  • 您的第二段似乎是在谈论带有捕获的 lambda,但 OP 的问题是关于无捕获的 lambda
  • 我同意@M.M 的观察,并想补充一点:捕获的 lambda 不能衰减为函数指针,所以原来的问题没有意义。事实上,有上下文的情况并没有更有趣,因为我们确切地知道会发生什么。这种情况就不太清楚了:它是等价于调用常规函数,还是等价于使用悬空的this 指针调用无状态对象成员函数(这是UB,但几乎可以肯定会工作)。
  • 好吧,现在问题变成了关于标准和UB的问题,而不是“实用问题”。所以我看到关于 lambdas 与上下文的部分变得越来越不相关。
猜你喜欢
  • 1970-01-01
  • 2021-08-12
  • 1970-01-01
  • 2015-11-25
  • 1970-01-01
  • 2020-02-13
  • 1970-01-01
  • 2016-10-09
  • 1970-01-01
相关资源
最近更新 更多