lambda的捕获变量被重置

时间:2018-01-13 16:37:34

标签: c++ c++11 lambda closures

我正在尝试在项目中使用lambda,但我认为我错过了关闭闭包的范围。我测试了这段代码,这在某种程度上简化了我的问题。

#include <iostream>
#include <functional>

using namespace std;

void tester_wrapper(std::function<int(void)> cb, int counter){
    if (counter == 10)
        return;
    else{
        cout << cb() << endl;
        tester_wrapper(cb, counter + 1);
    }
}

void tester(std::function<int(void)> cb){
    tester_wrapper(cb, 0);
}

int main()
{
    auto getNum = ([](int starter) {
        return [starter]() mutable {
            return ++starter;
        };
    })(1);

    tester(getNum);
    tester(getNum);
}

第一次调用tester后,重置捕获的变量starter,以便打印相同的输出两次。

为了避免lambda的内部计数器(starter)的这种行为,我该怎么做?基本上,对tester的第二次调用必须从12而不是2开始打印10个数字。

修改

感谢您的回答。我没有考虑过我将副本传递给tester_wrapper,所以我找到了这个解决方案:

#include <iostream>
#include <functional>

using namespace std;

std::function<int(void)> mylambda(int starter){
    return [starter]() mutable {
        return ++starter;
    };
}

void tester_wrapper(const std::function<int(void)>& cb, int counter){
    if (counter == 10)
        return;
    else{
        cout << cb() << endl;
        tester_wrapper(cb, counter + 1);
    }
}

void tester(const std::function<int(void)>& cb){
    tester_wrapper(cb, 0);
}

int main()
{
    /*auto getNum = ([](int starter) {
        return [starter]() mutable {
            return ++starter;
        };
    })(1);*/

    auto getNum = mylambda(1);

    tester(getNum);
    tester(getNum);
}

然而,现在我无法理解为什么旧的getNum使用“外部”函数(mylambda打印相同的输出而不同。

(我应该为此发布一个新问题吗?)

3 个答案:

答案 0 :(得分:2)

一种可能的解决方案是创建一个单独的变量starter然后通过引用捕获它,以便lambda更改实际变量:

auto starter = 0;

auto getNum = [&starter]() {
    return ++starter;
};

然后你打电话:

tester(getNum);
tester(getNum);

输出结果将是从120的数字。

答案 1 :(得分:2)

变量不会被重置,它是变量的不同副本。事实上,有一堆副本。第一个是你创建的lambda的状态。第二个是在构造第一个tester时创建的。您必须记住,它会将收到的可调用内容复制到自身中。因此,tester(std::ref(getNum)); tester(std::ref(getNum)); 的每次调用都会启动一系列副本。绕过它的一种方法是将lambda传递到std::reference_wrapper

getNum

将复制引用包装器,但所有副本都将引用相同的lambda对象getNum

现在,假设您打算创建许多不同的对象,例如std::functiontester及其提供的类型擦除是一种合理的操作过程,可以避免可能的代码膨胀。要记住的是不要制作多余的副本。因此tester_wrapper按价值接受是合法的,但gremlin> g.V(61464).project('father','mother','children'). by(out('father')). by(out('mother')). by(__.in('father').fold()) ==>[father:v[4344],mother:v[4152],children:[v[8440],v[12536],v[40964200]]] 应该通过引用接受。这样,您只需在API边界处为您需要的地方支付类型擦除费用。

答案 2 :(得分:2)

getNum正在复制您传递给tester的参数std::function<int(void)>。也就是说,std::function<int(void)>不存储原始getNum,而是存储副本

@StoryTeller和@Edgar已经提出了两个解决方案。这是第三个:

template<typename Callback>
void tester_wrapper(Callback && cb, int counter){
    if (counter == 10)
        return;
    else{
        cout << cb() << endl;
        tester_wrapper(cb, counter + 1);
    }
}

template<typename Callback>
void tester(Callback && cb){
    tester_wrapper(std::forward<Callback>(cb), 0);
}

现在没有副本,因为两个函数都通过引用接受参数。

顺便说一下,这个代码可能比其他两个代码更快,因为其他两个代码继续使用std:function,它有一个虚拟调用或等效函数来调用存储的可调用代码。

希望有所帮助。