首页 > 解决方案 > 如何与可变参数同时重用 C++ 闭包——类似于 JavaScript?

问题描述

JavaScript 创建一个具有创建它的范围的闭包。例如:

function startGreeter( name, iterations, delay ) {
    function greeter() {
        console.log("greetings, "+name+"! i="+iterations);
        if( --iterations )
            setTimeout( greeter, delay );
    }
    setTimeout( greeter, delay );
}

startGreeter("Alex",3,1000);
startGreeter("Beth",2,1500);

JavaScript 为 greeter() 函数创建了两个不同的闭包实例;一份给亚历克斯,一份给贝丝。当然,我们可以创建数十个或数百个这样的实例;例如,从一组数据中。

我想在 C++ 中完成同样的任务;具体来说,能够创建一个函数的 N 个不同的同时闭包实例。

void startGreeterTask( AsyncManager& asyncManager, std::string name, int iterations, longtime msDelay )
{
    TaskFunction greetingTask = [&]()
    {
        std::cout << "greetings, task " << name << "! iterations = " << iterations << std::endl;
        if( --iterations )
        {
            asyncManager.setTimeout( greetingTask, msDelay );
        }
    };
    asyncManager.setTimeout( greetingTask, msDelay );
}

int main( int argc, char* argv[] )
{
    AsyncManager asyncManager;
    startGreeterTask( asyncManager, "Alex", 3, 1000 );
    startGreeterTask( asyncManager, "Beth", 2, 1500 );
    asyncManager.execute();
}

AsyncManager 提供 setTimeout() 功能,如果我在 main() 范围内创建多个闭包,它就可以工作。但是在 startGreetingTask() 中,闭包函数 greetingTask() 的工作方式与 JavaScript 中的 greeter() 函数不同。据我了解,当 startGreeterTask() 完成执行时,greetingTask() 和所有引用的参数都超出了范围,因此释放了堆栈内存。第二次调用 startGreeterTask() 会造成堆栈内存的冲突使用,并确保出现段错误。

如何让 startGreeterTask() 像 startGreeter() 函数一样工作?

标签: c++closures

解决方案


感谢 Igor Tandetnik 为我指明了正确的方向。我在这里发布一个完整的答案供其他人查找。

解决方案是(a)默认使用复制捕获,(b)对我不想复制的类实例使用引用捕获,(c)添加mutable关键字以允许修改捕获的变量,以及(d)避免闭包函数对自身的引用。现在,闭包不再调用自身,而是调用定义闭包的函数。这种形式的自引用,通过一层间接,失去了一点 JavaScript 惯用语的灵活性和表达方式,但它功能齐全,很好地解决了问题。

也就是说,如果其他人解决了这个特定的闭包自引用问题,我希望看到解决方案。

void greetTask( AsyncManager& asyncManager, std::string name, int iterations, longtime msDelay )
{
    TaskFunction greetingClosure = [=,&asyncManager]() mutable
    {
        std::cout << "greetings, task " << name << "! iterations = " << iterations << std::endl;
        if( --iterations )
        {
            greetTask( asyncManager, name, iterations, msDelay );
        }
    };

    asyncManager.setTimeout( greetingClosure, msDelay );
}

推荐阅读