11

我一直在努力解决一个危害我的项目的 lambda 表达式问题。我找到了一个解决方案,但我想确切地了解它的工作原理和原因,以及它是否可靠。

#include <iostream>
#include <functional>
#include <unordered_map>

typedef std::function<const int&(const int&)> Callback;

int f(int i, Callback callback) {
    if (i <= 2) return 1;
    return callback(i-1) + callback(i-2);
}

int main(void) {

    std::unordered_map<int, int> values;

    Callback callback = [&](const int& i) {
        if (values.find(i) == values.end()) {
            int v = f(i, callback);
            values.emplace(i, v);
        }
        return values.at(i);
    };

    std::cout << f(20, callback) << std::endl;

    return 0;

}

我知道这是计算第 20 个斐波那契数的疯狂方式,但它是我能够阐述的最紧凑的 SSCCE。

如果我编译上面的代码g++ -O0并执行程序,我会得到6765,这实际上是第 20 个斐波那契数。如果我用 编译-O1-O2或者-O3我得到262144,那是垃圾。

如果我用 Valgrind 分析程序(用 编译-O0 -g),我会上Conditional jump or move depends on uninitialised value(s)线std::cout << f(20, callback) << std::endl;,但堆栈跟踪并没有说明任何有用的信息。

我不知道为什么我最终得到了这个:

Callback callback = [&](const int& i) -> const int& {

通过这个小小的修改,任何优化级别的编译都可以按预期进行,并且 Valgrind 没有报告任何问题。

你能帮我理解发生了什么吗?

4

2 回答 2

12

没有-> const int&lambda 的返回类型是int. 由于Callbackis的返回类型const int&callback最终返回对它用来保存 lambda 表达式返回值的临时对象的引用。未定义的行为。

对于这个简单的示例,简单的解决方法是根本不使用引用(Coliru 的示例),但我假设您的真实代码处理的重量级对象比int. 不幸的std::function是,当您分配一个返回非引用的函数时,没有指定警告,该函数std::function的返回类型引用。

于 2013-08-16T15:22:20.880 回答
3

凯西回答是正确的。我只是想补充一点。(它看到在 Casey 的好答案之上添加已成为我的常见行为;-)。)实际上,我的评论是gd1Casey帖子的评论。

我猜 OP 正在使用g++ -std=c++1y,因为在 C++11 中,显示的 lambda 的返回类型设置为void. 实际上,只有当 lambda 的主体包含单个return语句时,编译器才会推断返回的类型。否则,编译器假定此类型为void. 在 C++14 中,返回类型的自动推导(不仅适用于 lambda,也适用于函数)更强大。

正如人们所看到的n3582 ——该论文被批准用于 C++14 并且它gcc的实现是一个参考——指出

普通汽车从不推断参考,[...]

在 OP 的示例中,返回的类型是int(not const int&)。我相信人们可以使用-> decltype(auto)自动扣除产生参考。

于 2013-08-16T15:50:39.840 回答