Деструктор виджета Qt косвенно вызывает метод виджета через подключенный сигнал и вылетает - PullRequest
0 голосов
/ 13 мая 2018

Я создал виджет (QDataflowCanvas) на основе QGraphicsView, где я подключаю сигнал QGraphicsScene::selectionChanged() к гнезду MainWindow::onSelectionChanged моего главного окна:

void MainWindow::onSelectionChanged()
{
    // canvas is ptr to QDataflowCanvas, subclass of QGraphicsView
    auto selNodes = canvas->selectedNodes();
    auto selConns = canvas->selectedConnections();
    ...
}

Проблема возникает при закрытииmy MainWindow, и в QGraphicsView.

выбран какой-то элемент. Не думаю, что мне нужно предоставлять полный код (хотя его можно найти здесь ), так какЯ уже выделил причину сбоя.

Вот что произойдет (в порядке причинности):

  • деструктор MainWindow называется
  • деструктор QDataflowCanvasназывается
  • деструктор QGraphicsView называется
  • вызывается деструктор QGraphicsScene, который запускает удаление всех элементов (с clear())
  • деструктор QGraphicsItem называется
  • , который вызовет событие selectionChange
  • вызывается слот MainWindow :: onSelectionChanged
  • вызывается метод QDataflowCanvas :: selectedNodes (), но объект уничтожается
  • сбой!

, который можно увидеть более подробно по трассировке стека аварии:

stack trace from Qt Creator

Я нашел этот обходной путь: если я отключаюСигнал в MainWindow::~MainWindow, он, конечно, не будет аварийно завершать работу:

MainWindow::~MainWindow()
{
    QObject::disconnect(canvas->scene(), &QGraphicsScene::selectionChanged, this, &MainWindow::onSelectionChanged);
}

Но это кажется довольно нетипичной практикой: я никогда не сталкивался с необходимостью вручную устанавливать жесткие соединения между слотами сигнала, потому что в противном случае программа могла бы аварийно завершиться.

Для этого должно быть более правильное решение.

Ответы [ 2 ]

0 голосов
/ 14 мая 2018

Я воспринимал это так просто :) Как насчет проверки canvas указателя:

void MainWindow::onSelectionChanged()
{
    if (!qobject_cast<QGraphicsScene*>(canvas))
        return;

    auto selNodes = canvas->selectedNodes();
    auto selConns = canvas->selectedConnections();
    ...
}

Я использовал qobject_cast, чтобы проверить, существует ли еще указатель canvas.Вы можете проверить другим (лучше) способом.Код работает.

0 голосов
/ 14 мая 2018

Прежде всего, название вашего проекта является ошибкой. Пространство имен с префиксом Q занято. Вы не должны иметь никаких классов с префиксом Q ни в одном проекте, использующем Qt. Вы должны переименовать проект, например, в DataflowCanvas.

Существует три решения:

  1. Держите всех детей по значению, упорядочите детей в соответствии с их зависимостями. QWidgetPrivate::deleteChildren, вызываемый с QDataFlowCanvas, будет неактивным, или, по крайней мере, он не будет касаться объектов, которые вас интересуют.

  2. Используйте старый синтаксис connect при подключении к слоту MainWindow::onSelectionChanged. Обратите внимание, что когда ваш слот был вызван, объект главного окна имел динамический тип 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...