Qt小部件析构函数通过连接信号间接调用小部件方法,并崩溃
Posted
技术标签:
【中文标题】Qt小部件析构函数通过连接信号间接调用小部件方法,并崩溃【英文标题】:Qt widget destructor indirectly calls widget method via connected signal, and crashes 【发布时间】:2018-05-13 16:45:57 【问题描述】:我基于QGraphicsView
制作了一个小部件(QDataflowCanvas),我将信号QGraphicsScene::selectionChanged()
连接到我的主窗口的插槽MainWindow::onSelectionChanged
:
void MainWindow::onSelectionChanged()
// canvas is ptr to QDataflowCanvas, subclass of QGraphicsView
auto selNodes = canvas->selectedNodes();
auto selConns = canvas->selectedConnections();
...
当我关闭我的MainWindow
并且在QGraphicsView
中选择了某些项目时,就会出现问题。
我认为我不需要提供完整的代码(虽然可以找到 here),因为我已经隔离了崩溃的原因。
这就是将要发生的事情(按因果顺序):
MainWindow 的析构函数被调用 QDataflowCanvas 的析构函数被调用 QGraphicsView 的析构函数被调用 QGraphicsScene 的析构函数被调用,触发删除所有项目(clear()
)
QGraphicsItem 的析构函数被调用
将触发 selectionChange 事件
调用 MainWindow::onSelectionChanged 槽
方法 QDataflowCanvas::selectedNodes() 被调用,但对象被销毁
崩溃!
可以从崩溃的堆栈跟踪中看到更详细的信息:
我找到了这个解决方法:如果我断开MainWindow::~MainWindow
中的信号,它当然不会崩溃:
MainWindow::~MainWindow()
QObject::disconnect(canvas->scene(), &QGraphicsScene::selectionChanged, this, &MainWindow::onSelectionChanged);
但这似乎是一种相当不典型的做法:我从来没有发现自己必须手动严重的信号槽连接,否则程序会崩溃。
必须有更合适的解决方案。
【问题讨论】:
【参考方案1】:首先,您的项目名称是错误的。采用Q
-prefixed 命名空间。在任何使用 Qt 的项目中,您不应该有任何 Q
-prefixed 类。例如,您应该将项目重命名为 DataflowCanvas
。
有三种解决方案:
按值保留所有子项,根据子项的依赖关系对子项进行排序。从QDataFlowCanvas
调用的QWidgetPrivate::deleteChildren
将是无操作的,或者至少它不会触及您关心的对象。
连接到MainWindow::onSelectionChanged
插槽时使用旧的connect
语法。请注意,当您的插槽被调用时,主窗口对象是 QWidget
动态类型,而不是 MainWindow
类型。使用旧的连接语法建立的连接尊重对象的动态类型,与给定类的槽建立的连接将保证对象是该类的动态 ,即在运行时。
清除析构函数中的选择 - 然后将不再处理选择更改。
第一个解决方案使所有内容都变得明确,并且是我会使用的解决方案:
class DataFlowCanvas : public QGraphicsView
...
private:
QDataflowModel *model_;
QDataflowTextCompletion *completion_;
QSet<QDataflowNode*> ownedNodes_;
QSet<QDataflowConnection*> ownedConnections_;
QMap<QDataflowModelNode*, QDataflowNode*> nodes_;
QMap<QDataflowModelConnection*, QDataflowConnection*> connections_;
bool showIOletsTooltips_;
bool showObjectHoverFeedback_;
bool showConnectionHoverFeedback_;
qreal gridSize_;
bool drawGrid_;
QGraphicsSecene scene_;
;
场景在任何其他字段之前被破坏。问题解决了。你也应该按价值持有其他一切。例如。 completion_
等。指针间接没有用。
第二个解决方案突出了一个不幸的 Qt 错误。也就是说——在下面的代码中,旧的连接语法永远不会调用Derived2::aSlot2
,因为在调用槽时,对象不再是Derived2
类型:
#include <QtCore>
int ctr1, ctr2;
struct Derived1 : QObject
Q_SLOT void aSlot1() ctr1++; qDebug() << __FUNCTION__;
Q_SIGNAL void aSignal();
~Derived1() Q_EMIT aSignal();
Q_OBJECT
;
struct Derived2 : Derived1
Q_SLOT void aSlot2() ctr2++; qDebug() << __FUNCTION__ << qobject_cast<Derived2*>(this);
Q_OBJECT
;
int main()
Derived2 d;
QObject::connect(&d, &Derived2::aSignal, &d, &Derived2::aSlot2);
QObject::connect(&d, SIGNAL(aSignal()), &d, SLOT(aSlot2()));
QObject::connect(&d, SIGNAL(aSignal()), &d, SLOT(aSlot1()));
Q_ASSERT(ctr1 == 1);
Q_ASSERT(ctr2 == 1);
#include "main.moc"
输出清楚地说明了问题:
aSlot2 QObject(0x0) <-- aSlot2 called but `this` is of `Derived1*` type!
aSlot1
【讨论】:
感谢您的详细回答,非常有见地。我尝试在我的小部件类中按值保留QGraphicsSecene
,但我遇到了另一个相同类型的类似崩溃(基本上相同的模式:场景的析构函数删除所有对象,它发出一个由客户端处理的 selectionChange 信号,它尝试使用被销毁的小部件的方法)。似乎唯一的方法是清除小部件析构函数开头的选择。 (我还没有尝试解决方案#2)【参考方案2】:
我想得太简单了 :) 只检查canvas
指针怎么样:
void MainWindow::onSelectionChanged()
if (!qobject_cast<QGraphicsScene*>(canvas))
return;
auto selNodes = canvas->selectedNodes();
auto selConns = canvas->selectedConnections();
...
我使用qobject_cast
来检查指针canvas
是否仍然存在。您可以以其他(更好的)方式进行检查。代码有效。
【讨论】:
以上是关于Qt小部件析构函数通过连接信号间接调用小部件方法,并崩溃的主要内容,如果未能解决你的问题,请参考以下文章
从 C++ 中的多个线程调用 Qt 中小部件类的信号函数是不是安全?