指向 lambda 表达式的函数指针目标的生命周期是多久?

18
抱歉,这是一个冗长的问题,但让我把它分解一下:
C++标准是否保证:
void (*Ptr)(void) = [] {};
return Ptr;

这将仍然是定义行为吗?

我理解,在闭包中,它将被定义,因为该闭包对象通过值移动/复制;但是,虽然我知道“常规”函数具有无限/没有生命周期,但Ptr的目标是否也是如此?或者在每次lambda实例化时销毁并重新创建?

我关心的原因是,如果不行,我无法使用lambda作为回调函数。我想知道。


有趣的问题。由于指针将指向某个静态“调用者”成员,静态成员别无选择,只能使用一些虚拟对象来调用lambda的operator()(因为后者是非静态的)。该虚拟对象应该是局部于“调用者”的,或者是静态的,即在上述上下文中应该可以正常工作。但我并没有立即看到语言规范中的保证。语言规范甚至没有假定该虚拟对象的存在。 - AnT stands with Russia
4
为什么需要operator()?它只需要运行相同的代码即可。operator()为什么要被实现?可以通过调用operator void(*)()()返回的静态函数来实现operator() - Yakk - Adam Nevraumont
5
希望所有“冗长啰嗦”的问题都能像这个一样简短。 - M.M
我猜你的意思是问:如果调用代码通过返回的指针进行函数调用,这是否是已定义的行为? - M.M
C++14中的示例[expr.prim.lambda]/6显示operator fptr_t返回静态成员函数的地址。然而,我不确定该示例有多规范(从技术上讲,由于它在“注释”中,因此是非规范性的)。 - M.M
1
@M.M 所有的例子都不是规范性的,它们可以在注释中或不在注释中。 - T.C.
2个回答

10
对象有生命周期;函数没有。函数不会存在生命或死亡;它们总是存在的。因此,函数不能“超出范围”,之前有效的函数指针所指向的函数也不能消失。不管它们来自哪里,函数指针始终有效。
现在,这忽略了动态加载等内容,但那是额外的标准行为。
从lambda返回的函数指针是一个函数指针。它不是特殊或神奇的。因此,它与任何其他函数指针的行为没有区别。
这是一个更加复杂的问题。C++17标准对此似乎规定不够明确,但C++20已经更加清晰地表述了。C++17标准仅表示:“函数的地址在被调用时具有与闭包类型的函数调用运算符相同的效果。”“相同的效果”究竟意味着什么是个问题。可以说,“相同的效果”意味着执行函数调用运算符将执行相同的语句序列。也可以说,“相同的效果”意味着调用闭包对象本身。后一种情况可能听起来难以实现,但请记住,编译器魔法可以被使用。闭包可以返回由闭包根据请求分配的特定于实例的函数指针等内容。C++20标准更加清晰,部分原因在于无捕获lambda可以默认构造。

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

因此,在 C++20 中,标准明确表示调用函数指针与创建它的 lambda 对象的存在无关。


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

2
一个 lambda 函数只是一个真正函数或函数对象的语法糖(即具有 `operator()` 和一些成员的对象,在构造时通常定义)。因此,这样的函数或方法在编译期间静态定义。
尽管标准可能没有完全指定确切的实现方式,正如 @NicolBolas 指出的那样,似乎实际实现遵循严格的指导方针:没有上下文的 lambda 可以转换为普通函数指针,既不在 lambda 定义处,也不在调用处创建中间对象。我刚刚再次检查了 gcc 和 clang,我几乎可以确定 MSVC 也是这样做的。
注意:其余内容涉及有上下文的 lambda,虽然对我来说似乎更有趣和实用,但问题明确处理的是没有上下文的 lambda。
上下文存储在 lambda 中(将其视为具有某些有意义的参数的函数对象,在对象构造时接收)。因此,如果您传递一些引用或指向上下文的指针,则这些引用和指针(例如 `this`)不会自动延长其对应实体的生命周期。这就是为什么当您将 lambda 保存在与其定义不同的作用域中时,应该特别小心的原因。
在此已解决问题中,可以看到与lambda及其上下文范围定义相关的问题示例。检查修复程序,以了解为使存储的带有上下文的lambda安全所做的工作。

1
一个没有上下文的 Lambda 表达式总是会转换成一个匿名的静态函数。什么?不可能。auto f=[]{}; static_assert(std::is_same<decltype(f),void(*)()>{},"nope"); - Yakk - Adam Nevraumont
好的,你是对的。但是这样的lambda表达式仍然可以分配给相应的函数指针,就像主题发起者的例子一样。 - user3159253
4
好的,我会尽力进行翻译。您的第二段似乎在谈论带有捕获的lambda表达式,但OP的问题是关于没有捕获的lambda表达式。 - M.M
1
我同意@M.M的观察,并想补充一点:捕获变量的lambda不能衰减为函数指针,因此原始问题没有意义。实际上,具有上下文的情况并不是真正更有趣,因为我们确切地知道会发生什么。这种情况要清楚得多:它是否等同于调用常规函数,还是等同于调用带有悬空“this”指针的无状态对象成员函数(这是UB,但几乎肯定能工作)。 - Nir Friedman
现在问题已经转化为关于标准和UB而不是“实际问题”的问题。因此,我认为关于lambda的部分与上下文变得越来越不相关了。 - user3159253

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接