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&
,修复段错误。
另外,除非您有充分的理由这样做,否则不要使用 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
设为friend
或RF
。 (由于可以在封装的函数对象内部进行转换,因此可能不需要这样做。)
将它们放在一起并反转逻辑,我们可以在 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'派生的
使用 lambda 创建 std::function 会导致多余的 lambda 对象复制 - 为啥?
将 decltype 和 std::function 与 lambda 一起使用