【问题标题】:Segmentation fault with std::function and lambda parametersstd::function 和 lambda 参数的分段错误
【发布时间】: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 按值而不是按引用返回向量。
  • 如果你看到自己曾经返回一个引用,问问自己“我所指的那个对象在哪里生活”?如果你不能回答,你不应该这样做。
  • 请启用警告。您的编译器可能对您大喊这是一个坏主意。

标签: c++ function c++11 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?
    • 我来这篇文章是为了责怪某人,但没有人可以责怪:)
    猜你喜欢
    • 1970-01-01
    • 2019-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-16
    • 2019-09-04
    • 1970-01-01
    相关资源
    最近更新 更多