Создание экземпляров производных классов QWidget в производном классе QObject - PullRequest
0 голосов
/ 30 марта 2019

По какой-то причине компилятор Qt не компилируется, если вы пытаетесь передать производный класс QObject в качестве rparent в производный класс QWidget.

Как правильно предоставить родителя для производных классов QWidget в производном классе QObject? Я думаю о следующих решениях:

  • Используйте умный указатель с классами QWidget вместо предоставления объекту родителя.
  • Получите из QWidget вместо QObject (звучит неправильно для меня, поскольку класс не является виджетом).
  • Передайте экземпляр QWidget классу обслуживания QObject, у которого уже есть родитель, как я пытался продемонстрировать в следующем примере:
#include <QApplication>
#include <QtWidgets>

class ErrorMsgDialog;
class Controller;
class MainWindow;

// This is the QWidget class used in the QObject derived class (DataReadController)
class ErrorMsgDialog : public QDialog
{
    Q_OBJECT

public:
    explicit ErrorMsgDialog(QWidget *parent = nullptr)
        : QDialog(parent)
    {
        errorLbl = new QLabel("An unknown read error occured!");
        QHBoxLayout* layout = new QHBoxLayout;
        layout->addWidget(errorLbl);
        setLayout(layout);
        setWindowTitle("Error!");
        setGeometry(250, 250, 250, 100);
    }
    ~ErrorMsgDialog() { qDebug() << "~ErrorMsgDialog() destructed"; }

private:
    QLabel* errorLbl;
};

// QObject derived class - I want to instatiate Qwidget derived classes here, with this class as parent
class DataReadController
    : public QObject
{
    Q_OBJECT
public:
    DataReadController(QWidget* pw, QObject *parent = nullptr)
        : QObject(parent)
    {
        m_errorMsgDialog = new ErrorMsgDialog(pw);
        //m_errorMsgDialog = QSharedPointer<ErrorMsgDialog>(m_errorMsgDialog);
        //m_dataReader = new DataReader(this); m_dataReader->moveToThread(m_dataReaderThread); connect(....etc

        //simulate that DataReader emits an error msg
        QTimer::singleShot(2000, [&]() {
            onErrorTriggered();
        });
    }

public slots:
    void onNewDataArrived() {}

    // called if reader emits an error message
    void onErrorTriggered() { m_errorMsgDialog->show(); }
private:
    ErrorMsgDialog* m_errorMsgDialog;
    //QSharedPointer<ErrorMsgDialog> m_errorMsgDialog;
    //DataReader* m_dataReader;// DataReader is not shown here, it would be moved to a different thread and provide some data
};

// MainWindow
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr)
        : QMainWindow(parent)
    {
        parentWidget = new QWidget(this);   
        m_dataReadController = new DataReadController(parentWidget, this);
        setGeometry(200, 200, 640, 480);

        //Close after 5 seconds.
        QTimer::singleShot(5000, [&]() {
            close();
        });
    }

private:
    QWidget* parentWidget; // QWidget to pass to OBject derive class for parenting QWidgets
    DataReadController* m_dataReadController;
};

// Main
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

#include "main.moc"

Этот тест дает сбой, если я использую QSharedPointer с ErrorMsgDialog. Любые предложения о том, как это сделать? Может быть, ни одно из предложенных мной решений не является лучшим?

1 Ответ

1 голос
/ 01 апреля 2019

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

  1. локальные переменные (хранение и время жизни, управляемые областями)
  2. переменные-члены без указателей
  3. интеллектуальные указатели с четким владением (общий указатель) или без владения (слабый указатель).

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

Конечно, когда вступают в игру внешние библиотеки (например, наборы виджетов), я привязан к их API.

Что касается gtkmm 2.4, вышеупомянутые методы также работали довольно хорошо. GTKMM обеспечивает

  • умные указатели для разделяемых объектов
  • какое-то владение виджетами, относящимися к дочерним виджетам.

Когда я переключился на Qt, я увидел все new s и необработанные указатели в примерах учебника, которые немного меня испугали. После некоторых экспериментов я пришел к выводу, что смогу писать полнофункциональные приложения на Qt, аналогичные тем, которые я делал раньше в gtkmm & ndash; почти без необходимости new путем (снова) определения виджетов в качестве локальных переменных (например, в main()) или переменных-членов других классов, полученных (прямо или косвенно) из QWidget.

Кроме того, со временем я осознал общую концепцию Qt Деревья объектов и владение , но должен признать, что я редко полагаюсь на это в повседневной работе.

Относительно конкретной проблемы ОП:

A QWidget является производным от QObject. Следовательно, применяется обычный принцип владения QObject s. Кроме того, QWidget ожидает другого QWidget в качестве родителя. QWidget может быть родителем любого QObject s, но не наоборот.

Следовательно, я бы предложил следующее право собственности:

  • MainWindow является родителем DataReadController
  • MainWindow является родителем ErrorMsgDialog (создается в DataReadController).

DataReadController сохраняет указатель на ErrorMsgDialog как необработанный указатель. (Я полагаю, что право собственности, предоставленное QSharedPointer, вступит в конфликт с владением MainWindow.)

(Как уже было описано выше, в моем собственном программировании я бы попытался вообще запретить указатели и использовать (не указатели) переменные-члены. Однако, IMHO, это вопрос стиля и личных предпочтений.)

Модифицированный образец ОП testQParentship.cc:

#include <QtWidgets>

class Label: public QLabel {
  private:
    const QString _name;
  public:
    Label(const QString &name, const QString &text):
      QLabel(text),
      _name(name)
    { }

    virtual ~Label()
    {
      qDebug() << _name + ".~Label()";
    }
};

class HBoxLayout: public QHBoxLayout {
  private:
    const QString _name;
  public:
    HBoxLayout(const QString &name):
      QHBoxLayout(),
      _name(name)
    { }

    virtual ~HBoxLayout()
    {
      qDebug() << _name + ".~HBoxLayout()";
    }
};

class ErrorMsgDlg: public QDialog {
  private:
    const QString _name;
  public:
    ErrorMsgDlg(const QString &name, QWidget *pQParent):
      QDialog(pQParent),
      _name(name)
    {
      QHBoxLayout *pQBox = new HBoxLayout("HBoxLayout");
      pQBox->addWidget(
        new Label("Label", "An unknown read error occured!"));
      setLayout(pQBox);
      setWindowTitle("Error!");
    }

    virtual ~ErrorMsgDlg()
    {
      qDebug() << _name + ".~ErrorMsgDlg()";
    }
};

class DataReadCtrl: public QObject {
  private:
    const QString _name;
    ErrorMsgDlg *const _pDlgErrorMsg;

  public:
    DataReadCtrl(const QString &name, QWidget *pQParent):
      QObject(pQParent),
      _name(name),
      _pDlgErrorMsg(
        new ErrorMsgDlg(name + "._pDlgErrorMsg", pQParent))
    {
      //simulate that DataReader emits an error msg
      QTimer::singleShot(2000, [&]() {
        onErrorTriggered();
      });
    }

    virtual ~DataReadCtrl()
    {
      qDebug() << _name + ".~DataReadCtrl()";
    }

    void onErrorTriggered()
    {
      _pDlgErrorMsg->show();
    }
};

class MainWindow: public QMainWindow {
  private:
    const QString _name;
    DataReadCtrl *_pCtrlReadData;

  public:
    MainWindow(const char *name):
      QMainWindow(),
      _name(name),
      _pCtrlReadData(nullptr)
    {
      _pCtrlReadData
        = new DataReadCtrl(_name + "._pCtrlReadData", this);
      //Close after 5 seconds.
      QTimer::singleShot(5000, [&]() {
        qDebug() << _name + ".close()";
        close();
      });
    }

    virtual ~MainWindow()
    {
      qDebug() << _name + ".~MainWindow()";
    }
};

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup GUI
  MainWindow winMain("winMain");
  winMain.show();
  // runtime loop
  return app.exec();
}

и минимальный файл проекта Qt testQParentship.pro:

SOURCES = testQParentship.cc

QT += widgets

Скомпилировано и протестировано в cygwin64 в Windows 10:

$ qmake-qt5 testQParentship.pro

$ make && ./testQParentship
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQParentship.o testQParentship.cc
g++  -o testQParentship.exe testQParentship.o   -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread 
Qt Version: 5.9.4

Snapshot of testQParentship

После истечения срока действия QTimer::singleshot() in MainWindow главное окно закрывается. Результаты диагностики показывают, что дерево объектов разрушено должным образом (вместо того, чтобы «выбрасывать», когда ОС освобождает память процесса).

"winMain.close()"
"winMain.~MainWindow()"
"winMain._pCtrlReadData.~DataReadCtrl()"
"winMain._pCtrlReadData._pDlgErrorMsg.~ErrorMsgDlg()"
"HBoxLayout.~HBoxLayout()"
"Label.~Label()"
...