【问题标题】:QML: Lambda function works unexpectedlyQML:Lambda 函数意外工作
【发布时间】:2015-03-06 12:11:53
【问题描述】:

我认为 QML 支持 lambda 函数是因为 JavaScript 支持匿名函数以及函数是一流对象的事实,但它们并没有按我的预期工作。拿这个代码:

Item {
    property var items: []

    function handler( item ) {
        console.log( item );
    }

    Component.onCompleted: {
        for ( var i = 0; i < 3; ++i ) {
            var item = someObj.createObject();
            item.someValueChanged.connect( function() {
                handler( item ); } );

            items.push( item );
            console.log( "Adding:", item );
        }
    }

    Component {
        id: someObj

        Item {
            property bool someValue: false

            Timer {
                running: true
                onTriggered: {
                    parent.someValue = true;
                }
            }
        }
    }
}

我正在尝试使用 lambda function() { handler( item ); },以便在发出 someObj::someValueChanged 信号时,将发射项目传递给 handler( item ) 函数。

假设每个循环都会创建一个新的 lambda 实例,并且 item 引用将携带在该循环中创建的 someObj 实例的引用(即 item 将被 lambda 捕获)。但情况似乎并非如此,因为输出是:

qml: Adding: QQuickItem_QML_1(0x2442aa0)
qml: Adding: QQuickItem_QML_1(0x2443c00)
qml: Adding: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)

如您所见,要么在每个循环中替换整个函数,要么只替换 item 引用,因此最终只引用最后创建的 someObj。有人可以向我解释为什么 lambdas(如果它就是这样的话)不能按我期望的方式工作吗?这是 QML 问题,还是一般的 JavaScript 问题?

【问题讨论】:

    标签: javascript qt lambda qml


    【解决方案1】:

    试试这样的:

    item.someValueChanged.connect(function(capture) {
        return function() {
            handler(capture)}
    }(item))
    

    直观,对吧? :D

    如果 JS 使用“块作用域”,则每次循环迭代都会引用 3 个不同的 items,它会“按预期工作”。但是对于“函数范围”,只有一个 item 被引用,并且它引用了它的最终值,因此需要使用该 hack 及时“捕获”每个值。​​

    只是为了解释一下,如果不是很明显,信号连接到一个处理程序,该处理程序由一个函数仲裁,该函数将特定时间的参数值作为离散对象捕获,该对象用于馈送到处理程序。

    希望最初的 Qt 5.12 版本将通过引入对 let(也称为块范围变量)的支持来解决这个问题。

    更新:我可以确认使用 5.12,它现在可以按预期工作:

    let item = someObj.createObject(); // will produce 3 distinct obj refs
    

    【讨论】:

    • 啊,这是有道理的(无论如何对于 JS)。谢谢!
    • @cmannett85 - 如果您尝试运行延迟的 lambda,引用超出范围的函数的局部变量,在 C++ 中会发生什么?最好的情况是,如果它一开始就在堆栈上,并且之后堆栈没有达到那个深度并且内存没有被覆盖,它可能会工作,但很可能会崩溃。
    • True,但在 C++ 中您可以指定捕获的内容以及捕获方式。 JS 不支持的东西(至少是 QtQuick 使用的版本)。
    • @ddriver 正是因为这样做,我遇到了严重的崩溃。在 QML javascript 中,我将匿名函数作为插槽传递给来自 C++ 对象的信号。有时在函数超出范围并且系统崩溃(至少在 iOS 中)之后调用信号。您可以在here查看问题
    • 在 javascript 中捕获工作的方式是最不直观的方式
    【解决方案2】:

    dtech's answer 解决了这个问题(谢谢!),但我们可以简化它,以便更清楚地知道到底发生了什么。 “内部”函数需要匿名,但“外部”函数不需要。对外部函数使用普通函数可以使代码更容易理解,并且更容易自我记录,因为函数具有名称。外部函数创建一个信号处理程序,因此等效代码可以是:

    var signal_handler = create_signal_handler(item);
    item.someValueChanged.connect(signal_handler);
    
    ...
    
    function create_signal_handler(item)
    {
        return function() { return handler(item); }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-12
      • 2015-12-18
      • 2014-06-09
      相关资源
      最近更新 更多