在 QObject derived class 中实例化 QWidget derived classes
Instantiate QWidget derived classes in a QObject derived class
出于某种原因,如果您尝试将派生的 QObject class 作为派生对象传递给派生的 QWidget class。
,Qt 编译器将无法编译。
在 QObject 派生 class 中为 QWidget 派生 class 提供父对象的正确方法是什么?我正在考虑以下解决方案:
- 在 QWidget classes 中使用智能指针,而不是给对象一个父对象。
- 从 QWidget 而不是 QObject 派生(我觉得不对,因为 class 不是小部件)。
- 将 QWidget 实例传递给已具有父对象的 QObject 服务 class,正如我在下面的示例中尝试演示的那样:
#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 一起使用,此测试会崩溃。
关于执行此操作的任何建议?也许 none 我建议的解决方案是最佳实践?
动态创建的对象的正确存储和生命周期管理在 C++ 中并不容易。经过长时间的努力,我开始偏爱某些我仍然使用得相当成功的技术:
- 局部变量(由作用域管理的存储和生命周期)
- 非指针成员变量
- 具有明确所有权(共享指针)或非所有权(弱指针)的智能指针。
有了这个,我几乎完全禁止了我的源代码中的原始指针,从而使代码维护更友好,调试也更少烦人。
当然,当外部库(例如小部件集)发挥作用时,我就会绑定到它们的 API。
关于 gtkmm 2.4,上述技术也很有效。 gtkmm 提供
- 可共享对象的智能指针
- 与子小部件有关的小部件的某种所有权。
当我切换到 Qt 时,我看到了教程示例中的所有 new
和原始指针,这让我有点害怕。经过一些实验,我得出结论,我将能够编写功能齐全的 Qt 应用程序,就像我以前在 gtkmm 中所做的那样——几乎不需要 new
通过(再次)将小部件定义为局部变量(例如在 main()
) 或其他 类 的成员变量(直接或间接)派生自 QWidget
.
除此之外,随着时间的推移,我意识到了 Object Trees & Ownership 的一般 Qt 概念,但我必须承认,我在日常业务中很少依赖它。
关于OP的具体问题:
A QWidget
派生自 QObject
。因此,QObject
的通常所有权原则适用。此外,QWidget
期望另一个 QWidget
作为父级。 QWidget
可以是任何 QObject
的父级,但反之则不行。
因此,我建议以下所有权:
MainWindow
是 DataReadController
的父级
MainWindow
是 ErrorMsgDialog
的父级(在 DataReadController
中创建)。
DataReadController
将指向 ErrorMsgDialog
的指针存储为原始指针。 (我相信 QSharedPointer
提供的所有权会与 MainWindow
的所有权发生冲突。)
(如上所述,在我自己的编程中,我会尽量避免使用指针,而是使用(非指针)成员变量。但是,恕我直言,这是风格和个人喜好的问题。)
OP修改样本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
在 Windows 10 的 cygwin64 中编译和测试:
$ 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
MainWindow
中的QTimer::singleshot()
到期后,主window关闭。诊断的输出说明对象树被正确破坏(而不是 "thrown" 在 OS 释放进程内存时消失)。
"winMain.close()"
"winMain.~MainWindow()"
"winMain._pCtrlReadData.~DataReadCtrl()"
"winMain._pCtrlReadData._pDlgErrorMsg.~ErrorMsgDlg()"
"HBoxLayout.~HBoxLayout()"
"Label.~Label()"
出于某种原因,如果您尝试将派生的 QObject class 作为派生对象传递给派生的 QWidget class。
,Qt 编译器将无法编译。在 QObject 派生 class 中为 QWidget 派生 class 提供父对象的正确方法是什么?我正在考虑以下解决方案:
- 在 QWidget classes 中使用智能指针,而不是给对象一个父对象。
- 从 QWidget 而不是 QObject 派生(我觉得不对,因为 class 不是小部件)。
- 将 QWidget 实例传递给已具有父对象的 QObject 服务 class,正如我在下面的示例中尝试演示的那样:
#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 一起使用,此测试会崩溃。 关于执行此操作的任何建议?也许 none 我建议的解决方案是最佳实践?
动态创建的对象的正确存储和生命周期管理在 C++ 中并不容易。经过长时间的努力,我开始偏爱某些我仍然使用得相当成功的技术:
- 局部变量(由作用域管理的存储和生命周期)
- 非指针成员变量
- 具有明确所有权(共享指针)或非所有权(弱指针)的智能指针。
有了这个,我几乎完全禁止了我的源代码中的原始指针,从而使代码维护更友好,调试也更少烦人。
当然,当外部库(例如小部件集)发挥作用时,我就会绑定到它们的 API。
关于 gtkmm 2.4,上述技术也很有效。 gtkmm 提供
- 可共享对象的智能指针
- 与子小部件有关的小部件的某种所有权。
当我切换到 Qt 时,我看到了教程示例中的所有 new
和原始指针,这让我有点害怕。经过一些实验,我得出结论,我将能够编写功能齐全的 Qt 应用程序,就像我以前在 gtkmm 中所做的那样——几乎不需要 new
通过(再次)将小部件定义为局部变量(例如在 main()
) 或其他 类 的成员变量(直接或间接)派生自 QWidget
.
除此之外,随着时间的推移,我意识到了 Object Trees & Ownership 的一般 Qt 概念,但我必须承认,我在日常业务中很少依赖它。
关于OP的具体问题:
A QWidget
派生自 QObject
。因此,QObject
的通常所有权原则适用。此外,QWidget
期望另一个 QWidget
作为父级。 QWidget
可以是任何 QObject
的父级,但反之则不行。
因此,我建议以下所有权:
MainWindow
是DataReadController
的父级
MainWindow
是ErrorMsgDialog
的父级(在DataReadController
中创建)。
DataReadController
将指向 ErrorMsgDialog
的指针存储为原始指针。 (我相信 QSharedPointer
提供的所有权会与 MainWindow
的所有权发生冲突。)
(如上所述,在我自己的编程中,我会尽量避免使用指针,而是使用(非指针)成员变量。但是,恕我直言,这是风格和个人喜好的问题。)
OP修改样本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
在 Windows 10 的 cygwin64 中编译和测试:
$ 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
MainWindow
中的QTimer::singleshot()
到期后,主window关闭。诊断的输出说明对象树被正确破坏(而不是 "thrown" 在 OS 释放进程内存时消失)。
"winMain.close()"
"winMain.~MainWindow()"
"winMain._pCtrlReadData.~DataReadCtrl()"
"winMain._pCtrlReadData._pDlgErrorMsg.~ErrorMsgDlg()"
"HBoxLayout.~HBoxLayout()"
"Label.~Label()"