std::function 和 lambda 参数的分段错误

Posted

技术标签:

【中文标题】std::function 和 lambda 参数的分段错误【英文标题】:Segmentation fault with std::function and lambda parameters 【发布时间】:2018-02-14 20:13:32 【问题描述】:

您能解释一下为什么这段代码会崩溃吗?我希望输出“a”,但出现分段错误。

#include <functional>
#include <iostream>
#include <vector>
#include <string>


using namespace std;

struct MyStruct 
  vector<string> a;
  vector<string> b;
;

void out_data(const MyStruct& my_struct, const std::function<const vector<string>&(const MyStruct&)> getter) 
  cout << getter(my_struct)[0] << endl;


int main(int argc, char** argv)

  MyStruct my_struct;
  my_struct.a.push_back("a");
  my_struct.b.push_back("b");
  out_data(my_struct, [](const MyStruct& in) return in.a;);
  return 0;

【问题讨论】:

可能是因为 lambda 按值而不是按引用返回向量。 如果你看到自己曾经返回一个引用,问问自己“我所指的那个对象在哪里生活”?如果你不能回答,你不应该这样做。 请启用警告。您的编译器可能对您大喊这是一个坏主意。 【参考方案1】:

[](const MyStruct& in) return in.a;

lambda 表达式 等价于

[](const MyStruct& in) -> auto return in.a;

返回in.a 的副本。然后,您的 std::function 签名会返回对本地对象的悬空引用。


lambda 表达式更改为

[](const MyStruct& in) -> const auto& return in.a;

改为返回const&amp;,修复段错误。


另外,除非您有充分的理由这样做,否则不要使用 std::function 传递 lambda。我建议阅读我关于该主题的文章:"passing functions to functions"

【讨论】:

【参考方案2】:

我责怪std::function(还有你)。当然,我责怪您要求 std::function 返回悬空引用,正如 Vittorio Romeo 所解释的那样。但我也责怪std::function 的构造函数模板没有检查这种情况,这种情况在大多数或所有情况下都应该在编译时检测到,从而生成诊断。 (我使用“责备”这个词只是为了指出可能的改进领域。从这个意义上说,我也责怪自己没有考虑在我自己的unique_function 类模板的实现中添加这种精确的检查。)

让我们仔细看看构造函数的签名。为此,我选择了定义站点。

template<typename R, typename... Args>
template<typename F>
std::function<R(Args...)>::function(F f);

这里应该可以禁止悬空引用。当且仅当R 是对F 返回的临时对象的引用时,才会从operator() 返回悬空引用。让我们定义(在构造函数体的范围内):

using RF = decltype(f(std::forward<Args>()...));

现在我们几乎可以肯定,如果:

std::is_reference<R>::value && ! std::is_reference<RF>::value

但有一个问题,RF 可能是一个类类型,其中包含用户定义的转换运算符到R。尽管这种转换可能仍然不安全,但我们目前没有足够的信息来决定,并且应该在一般性方面犯错。显然,我们可以检测R的目标是否是RF的公共基类(这是假设上述条件为真):

std::is_convertible<RF *, typename std::remove_reference<R>::type *>::value

这里我只允许公共继承,因为std::function 只能访问公共的非歧义基类。除非有人出于某种奇怪的原因将std::function 设为friendRF。 (由于可以在封装的函数对象内部进行转换,因此可能不需要这样做。)

将它们放在一起并反转逻辑,我们可以在 function 构造函数的主体前加上:

using RF = decltype(f(std::forward<Args>()...));
static_assert( ! std::is_reference<R>::value  ||
                 std::is_reference<RF>::value ||
               ! std::is_convertible<RF *, typename std::remove_reference<R>::type *>::value,
               "Using this function object would result in a dangling reference in the function call" );

【讨论】:

尽管这不是一个答案,但我赞成,因为它是有价值的信息。您是否考虑过进一步研究并撰写提案/DR? 我来这篇文章是为了责怪某人,但没有人可以责怪:)

以上是关于std::function 和 lambda 参数的分段错误的主要内容,如果未能解决你的问题,请参考以下文章

错误“lambda不是从'std :: function'派生的

std::function 作为模板参数

使用 lambda 创建 std::function 会导致多余的 lambda 对象复制 - 为啥?

将 decltype 和 std::function 与 lambda 一起使用

将不可复制的闭包对象传递给 std::function 参数

std::function()函数std::bind()函数以及lambda