使用 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
将始终在堆上分配。
所以在实践中,我不确定 QObject
与 QObject *
是否有任何有意义的区别。
【讨论】:
谢谢。我应该更清楚 MyClass 默认情况下也在堆栈中的部分。关于您对没有意义的差异的评论。我假设堆栈或堆上的几个字节不会有任何显着的性能差异。只需遵循良好做法,不要使用我不需要的东西。如果我不需要为此目的显式使用堆,那么我不会偏离默认的堆栈使用。以上是关于使用 QObject 作为类成员并将该成员用作连接的上下文是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章
如何将具有指向 QML 的指针成员的非 QObject 类公开?