【问题标题】:Qt widget destructor indirectly calls widget method via connected signal, and crashesQt小部件析构函数通过连接信号间接调用小部件方法,并崩溃
【发布时间】: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);
}

但这似乎是一种相当不典型的做法:我从来没有发现自己必须手动严重的信号槽连接,否则程序会崩溃。

必须有更合适的解决方案。

【问题讨论】:

    标签: c++ qt


    【解决方案1】:

    首先,您的项目名称是错误的。采用Q-prefixed 命名空间。在任何使用 Qt 的项目中,您不应该有任何 Q-prefixed 类。例如,您应该将项目重命名为 DataflowCanvas

    有三种解决方案:

    1. 按值保留所有子项,根据子项的依赖关系对子项进行排序。从QDataFlowCanvas 调用的QWidgetPrivate::deleteChildren 将是无操作的,或者至少它不会触及您关心的对象。

    2. 连接到MainWindow::onSelectionChanged 插槽时使用旧的connect 语法。请注意,当您的插槽被调用时,主窗口对象是 QWidget 动态类型,而不是 MainWindow 类型。使用旧的连接语法建立的连接尊重对象的动态类型,与给定类的槽建立的连接将保证对象是该类的动态 ,即在运行时。

    3. 清除析构函数中的选择 - 然后将不再处理选择更改。

    第一个解决方案使所有内容都变得明确,并且是我会使用的解决方案:

    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 是否仍然存在。您可以以其他(更好的)方式进行检查。代码有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-12
      • 2014-06-15
      • 2011-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多