Q_PROPERTY NOTIFY 信号及其参数

Posted

技术标签:

【中文标题】Q_PROPERTY NOTIFY 信号及其参数【英文标题】:Q_PROPERTY NOTIFY signal and its argument 【发布时间】:2017-09-28 01:22:39 【问题描述】:

我习惯用参数编写我的“propertyChanged”signals,这样接收端就不需要显式调用Q_PROPERTYREAD 函数。

我这样做是出于清楚和假设,即在 QML 数据绑定情况下,不需要对 getter 进行“昂贵的”调用来实际获取值,因为它已经作为信号参数传递给 QML。

我的同事不同意,并说这违反了“QML 风格”,对此我做出了回应,文档中明确指出它可能有一个参数会采用基础成员的新值:

MEMBER 变量的NOTIFY 信号必须采用零个或一个参数,该参数必须与属性的类型相同。该参数将采用属性的新值。

文档中没有任何地方说明 QML 绑定系统使用此参数来防止在处理信号时对 getter 进行额外的函数调用。我知道这个调用可能会从 C++ 进行,因此不会对 C++ 进行“昂贵的”QML 调用,但它仍然是一个额外的函数调用,原则上可能会在多次更新的情况下导致明显的性能损失。

我尝试检查 QML 绑定源代码,但无法从中推断出任何东西。我想知道是否有人知道交易是什么:是否使用了信号参数?

【问题讨论】:

***.com/questions/42870182/… 请提供一个(代码)示例来演示您感兴趣的场景。 【参考方案1】:

假设在 QML 数据绑定情况下,没有“昂贵”的调用 需要对 getter 进行实际获取值,因为它是 已作为信号参数传递给 QML。

从技术上讲,不太可能有任何与您描述的相似的东西。这没有任何意义。如果您的 getter 很昂贵,您应该注意以更简单的形式缓存结果,并根据更改或按需更新。

如果 QML 绑定只是单个属性到单个属性,那么这样的优化可能是有意义的。但是 QML 中的绑定实际上是匿名表达式,它的工作方式是当表达式引用的对象的任何更改通知触发其重新评估时。在这种情况下,它会给实现增加不必要的复杂性,从通知信号发送一个值以保留,而其他值则必须调用 getter。

显然,我只是在这里假设。让我怀疑这种优化是否存在的另一件事是属性绑定系统并不是那么复杂或专注于效率。从属性值相互依赖的情况下,它甚至无法消除冗余评估这一事实可以看出这一点。我已经看到声称存在这样的优化,但我已经测试过,它甚至无法避免最简单的绑定树冗余。

当然,如果您坚持使用 MEMBER 属性,这并不容易证明,因为 getter 是自动生成的,您无法在其中插入调试语句。

但是,如果您尝试将其用于常规属性,您会发现即使信号发出实际值,getter 仍然会被调用。绝对没有理由自动生成的 getter 会得到不同的处理。

如链接答案中所述,如果您需要为代码的 C++ 部分发出的值,请保留,它不会干扰 QML 部分。如果不是,那么根本不发出该值,即使很小,它仍然是开销。发出值是 C++ 方式,绑定是一个根本不同的概念,在 C++ 中并不真正适用(并非没有大量冗长),QML 方式不需要发出改变的值。

所以你们都错了。它并不是真正的“反对 QML 风格”,因为它不会阻碍任何事情,并且可以选择在文档中发出值绝不表明它“符合 QML 风格”,或者它得到任何特殊待遇。所以如果这就是你这样做的原因,你不妨停下来,因为它完全是多余的。

【讨论】:

与其说 getter 很昂贵,不如说是从 QML 对 getter 的额外调用将​​成为假设的“热点”。 @rubenvb - 基于另一个答案,我认为您应该澄清“QML 数据绑定”的含义-您是指实际属性绑定还是信号处理程序-附加的表达式到onPropertyChanged? 我的意思是数据(属性)绑定:与使用 QML Binding 类型得到的结果相同。我只是在谈论与 Q_PROPERTY 相关的事情。 @Drechterland 米奇发布的答案似乎提供了相反的证据。 米奇提供的答案仅证明信号参数可以在信号处理程序中使用......就像...... adoy :) 他的代码没有绑定情况,所以我看不出它如何可能建立任何关于绑定情况的信息。如果在绑定中使用了属性,并且如果该属性发生更改,则绑定重新评估将调用 getter,而不管通知信号是否发出属性值。如果您不信任我,请测试一下。【参考方案2】:

我想知道是否有人知道交易是什么:是否使用了信号参数?

这是一种检查方法:

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QDebug>

class MyType : public QObject

    Q_OBJECT
    Q_PROPERTY(bool foo READ foo WRITE setFoo NOTIFY fooChanged)

public:
    MyType(QObject *parent = nullptr) :
        QObject(parent),
        mFoo(0)
    
    

    bool foo() const
    
        qDebug() << Q_FUNC_INFO;
        return mFoo;
    

    void setFoo(bool foo)
    
        if (foo == mFoo)
            return;

        mFoo = foo;
        emit fooChanged(mFoo);
    

signals:
    void fooChanged(bool foo);

private:
    bool mFoo;
;

int main(int argc, char *argv[])

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication app(argc, argv);

    qmlRegisterType<MyType>("App", 1, 0, "MyType");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QLatin1String("qrc:/main.qml")));

    return app.exec();


#include "main.moc"

ma​​in.qml:

import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 2.0

import App 1.0

Window 
    width: 400
    height: 400
    visible: true

    Switch 
        id: fooSwitch
    

    MyType 
        id: myType
        foo: fooSwitch.checked
        onFooChanged: print("onFooChanged, foo =", foo)
//        onFooChanged: print("onFooChanged myType.foo =", myType.foo)
    

来回切换时的输出为:

qml: onFooChanged, foo = true
qml: onFooChanged, foo = false

所以可以肯定地说使用的是值而不是 getter。

要查看未使用信号参数时的输出结果,请取消注释掉注释掉的行并注释掉另一行:

bool __cdecl MyType::foo(void) const
qml: onFooChanged myType.foo = true
bool __cdecl MyType::foo(void) const
qml: onFooChanged myType.foo = false

【讨论】:

我认为你看错了。您直接将处理程序附加到信号,因此它使用传递的参数。但这并不是真正的“有约束力的情况”。在绑定表达式中使用myType.foo 时会出现绑定情况。例如property bool test: myType.foo &amp;&amp; someOtherBool - 在这种情况下,当foo 发生变化并且重新计算绑定表达式时,它不会使用信号传递的值,而是调用getter。 onFooChanged 中,foo 参数隐藏了foo 属性。因此,您没有调用属性 getter。试试onFooChanged: print("onFooChanged, foo =", myType.foo)。显然,我对“绑定”和“处理程序”进行了区分——处理程序可以访问信号的参数,如果有的话,你可以有多个。但这对属性值更改时如何重新评估绑定没有任何影响。 你是对的;我认为这个问题是关于信号处理程序的,但现在我重新阅读它,我对他们在问什么感到很困惑。我认为 OP 应该使用代码 sn-p 提供一些说明。另外,您在第二条评论中建议的代码已经在 sn-p 中,这是答案的最后一部分。 好吧,OP 重申他不是在询问处理程序,但仍有一些歧义。但无论如何,在优化绑定表达式评估的上下文中,使用通知信号传递参数似乎没有任何好处。您的答案集中在我在链接答案中所说的内容 - 发出一个值可以更容易地获取该值而无需引用对象和属性 getter,这在 C++ 中很有意义,因为实现一个值要困难得多类似于您通过绑定在 QML 中所做的事情。但这是通用信号,而不是严格的属性。【参考方案3】:

onPropertyChanged-signal 中传递已更改属性 虽然可能,但肯定不是QML 样式

如果是这样,那么您应该期望至少对于它实现的基本类型(很容易显示),它不是。

basictypes.qml

import QtQuick 2.7
import QtQuick.Controls 2.0

ApplicationWindow 
    id: root
    visible: true
    width: 400; height: 450

    property int num: 5
    Button 
        text: num
        onClicked: num += 1
    
    onNumChanged: console.log(JSON.stringify(arguments), arguments.length)

正如您在输出中看到的那样,即使您更改了一种最基本的类型,例如int,也没有传递任何参数。

如果现在 QML 将使用可选但很少实现的传递值 this 会产生开销,因为您总是需要在使用之前检查参数是否存在。虽然一个简单的检查并不昂贵,但如果它通常评估为false,然后您使用解决方法,为什么要事先这样做?

虽然我可能不排除在官方 realse 中的任何 onPropertyChanged-signals 中有任何传递的值,但在 QML 中添加的带有 property [type] [name] 的属性没有。大多数继承属性也没有(测试按钮:textwidthheight)。

【讨论】:

几乎不可能传递任何由值派生的QObject “检查是否传递了值”可以使用模板和 C++ 端的一些 SFINAE 来处理,这不会产生任何开销。 @dtech,你是对的,我错了。有一个引用传递给对象,属性指向。 @rubenvb:如果你已经预编译了所有东西,也许。但如果你把它推到 JIT,我不这么认为。【参考方案4】:

请注意,在 QtCreator 中通过右键单击属性 foo 并选择“重构/生成缺少的 Q_PROPERTY 成员”生成的代码

Q_PROPERTY(bool foo READ foo NOTIFY fooChanged)

生成包含属性的信号

void fooChanged(bool foo);

所以我认为这 Qt/QML 风格

【讨论】:

这是一个很好的观点。也就是说,QtCreator 实际上与此完全相反!至少在 Creator v.5 中。

以上是关于Q_PROPERTY NOTIFY 信号及其参数的主要内容,如果未能解决你的问题,请参考以下文章

使用信号或 Q_PROPERTY 更新 QML 对象

从 C++ 向 QML 发出信号以读取 Q_PROPERTY 是同步事件吗?

Qt中Q_PROPERTY定义属性以及属性的使用

用信号量及其PV操作处理实际问题

用信号量及其PV操作处理实际问题

更改的属性不触发信号