QML:Lambda 函数意外工作

Posted

技术标签:

【中文标题】QML:Lambda 函数意外工作【英文标题】:QML: Lambda function works unexpectedly 【发布时间】: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 问题?

【问题讨论】:

【参考方案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); 

【讨论】:

以上是关于QML:Lambda 函数意外工作的主要内容,如果未能解决你的问题,请参考以下文章

定义 QML 信号参数类型时出现意外的 Token `:'

在AWS Lambda中获得“内部流位置意外更改”

使用 Python 的三元运算符与 lambda 组合的意外输出

AWS Lambda 上的 Headless Chrome Python 3.8 - 意外退出。状态码是:127

从 Spark sql Windows 函数获得意外结果

应用`std::transform()`函数后向量元素的意外行为[重复]