使用 QObject 作为类成员并将该成员用作连接的上下文是不是安全?

Posted

技术标签:

【中文标题】使用 QObject 作为类成员并将该成员用作连接的上下文是不是安全?【英文标题】:Is it safe to use a QObject as a class member and use that member as the context for connect?使用 QObject 作为类成员并将该成员用作连接的上下文是否安全? 【发布时间】:2019-07-11 23:11:17 【问题描述】:

我有一个“普通”(非 qt 派生)类。它正在获取传递给它的 QObject 引用。在这个类中,我想将输入的 QObject 信号与 lambda 连接起来。 到目前为止一切都很好,除非我的类的对象在传入 QObject 之前被销毁。来自 QObject 的信号现在指向一个不再存在的 lambda。

通常的做法是我的类继承自 QObject,并将this 作为上下文传递给connect。当我的对象死亡时,连接将被 Qt 代码自动销毁。

解决这个问题的另一种方法是保存连接的返回结果,一个QMetaObject::Connection,然后在我的类调用QObject::disconnect(resultOfConnect)的析构函数中。

现在我想如何解决它是这样的:

class SomeQtDerivedClass : public QObject ...

class MyClass

public:
    MyClass(SomeQtDerivedClass& qtObject)
    
        connect(&qtObject, &qtObject::someSignal, &m_QtObject, []()blahblah)
    
private:
    QObject m_QtObject;

我没有看到这种模式在任何地方使用过,我在官方文档中也找不到关于这种方法的任何内容。如果我可以使用组合,我不想从 QObject 继承。如果我可以在堆栈上创建 m_QtObject,我不想在堆上创建它。如果我的类对象在输入的 QObject 之前被破坏,我希望连接被自动破坏。

这行得通吗?

【问题讨论】:

嗯...it's not exactly safe. 您能解释一下您的意思吗?它如何与我的描述相符? 总结该链接,在MyClass被销毁的同时可能有一个排队事件发生,这导致了UB。 @Justin 这通常是QObject 和排队事件的问题,与此设置无关。 【参考方案1】:

它应该可以正常工作(当然,只要您不在m_QtObject 上设置父级); m_QtObject 被你的类销毁(在用户提供的析构函数之后,如果有的话),所以它与你从 QObject 派生几乎没有什么不同(事实上,从许多角度来看,基类真的类似于隐藏的第一个类成员)。

话虽如此,我会使用QMetaObject::Connection(可能将其包装成std::unique_ptr或其他东西以在销毁时自动断开连接); QObject 相当重量级,用它来利用它的自动断开功能似乎有点浪费。

【讨论】:

即使设置父级也可以,只要您确保父级不会删除对象(通过使父级比子级活得更长,或者在父级获取之前从子级中删除父级)破坏)。 QObject 是相当重量级的。您能否为此添加一些细节? @vuko_zrno:基本上,@BenjaminT 所说的。 QObject 至少需要超过 100 字节内存的堆分配(因为它是基于 PIMPL 的),此外它还包含所有类型的额外内容(父/子链接、计时器、属性映射......)需要更多的分配。如果你不需要它的东西,就不要使用它。【参考方案2】:

这不是对问题的直接回答,但它回答了对@vuko_zrno 的误解。

拥有QObject 作为成员(反对QObject *)并不能保证您的堆栈分配。它只保证 QObject 将使用与拥有它的 MyClass 实例相同的内存块。

例如,如果您执行MyClass obj;,则 MyClass 和 QObject 都将在堆栈上。但是如果你使用MyClass * obj = new MyClass;,它们都会在堆上。

还有一点非常重要,Qt 类大量使用了 pimpl 设计模式。这意味着QObject 类只是一个空壳,它的所有成员变量都存储在另一个名为QObjectPrivate 的类中。

实际上QObject 的大小在 64 位上是 16 字节,而QObjectPrivate 是 112 字节。并且QObjectPrivate 将始终在堆上分配。

所以在实践中,我不确定 QObjectQObject * 是否有任何有意义的区别。

【讨论】:

谢谢。我应该更清楚 MyClass 默认情况下也在堆栈中的部分。关于您对没有意义的差异的评论。我假设堆栈或堆上的几个字节不会有任何显着的性能差异。只需遵循良好做法,不要使用我不需要的东西。如果我不需要为此目的显式使用堆,那么我不会偏离默认的堆栈使用。

以上是关于使用 QObject 作为类成员并将该成员用作连接的上下文是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章

如何将具有指向 QML 的指针成员的非 QObject 类公开?

std::mutex 作为类成员,并将类对象存储到容器中

编译器错误 C2248:“QObject::Qobject”:无法访问在类“QObject”中声明的私有成员

如何创建新结构并将其原始值用作新结构的成员

Qt入门教程QObject篇QObject类

Qt入门教程QObject篇QObject类