为什么 qt 应用程序总是新的小部件而不是在堆栈上?
why qt application always new widgets instead of on stack?
那里。我从 qt 看到了这个例子:https://doc.qt.io/qt-5/qtwidgets-widgets-calculator-example.html,想知道为什么 qt 总是用 new 创建小部件而不是在堆栈上分配
Button *Calculator::createButton(const QString &text, const char *member)
{
Button *button = new Button(text);
connect(button, SIGNAL(clicked()), this, member);
return button;
}
在堆栈上创建小部件更快,在这个问题中:
Mike的回答说create on stack完全没问题,但是为什么官方文档大多用new呢?
这很简单:因为您希望小部件即使在此功能范围完成后仍然存在并且 QObject
s 不可复制。以下代码不起作用,因为您无法复制 object.
Button Calculator::createButton(const QString &text)
{
Button button(text); // allocated on stack, it is OK so far...
return button; // nope, you cannot copy that! it does not compile.
}
以下代码也不起作用,因为您将传递已删除的 object。
Button *Calculator::createButton(const QString &text)
{
Button button(text); // allocated on stack, it is OK so far...
return &button; // ... returning pointer, still OK...
// Oh no! End of scope here, the object gets murdered now.
// whoever holds the pointer to it, holds a dead, invalid object.
}
object 在范围末尾被删除,您将传递一个指向已释放的指针,因此内存无效。
你提到的例子
class Widget2 : public QWidget
{
Q_OBJECT
public:
Widget2(QWidget* parent = nullptr);
private:
QPushButton button;
};
并不一定意味着 button
是在堆栈上创建的。这取决于 Widget2
的实例是如何创建的。如果它在堆栈上,那么 button
也在堆栈上。如果它在堆上 new
ed,那么 button
也在堆上。与声明 button
并将其保留为指针的“正常”情况的不同之处在于,在上面的示例中,它们在一次分配中一起分配。是的,它的速度可以忽略不计,但这只是一个过早的优化。小部件的分配永远不会成为您的瓶颈,相信我。顺便提一句。 Qt 中的大多数 objects 都是使用 PIMPL idiom 来实现的,以保持二进制兼容性,所以它们在内部分配他们私有的 objects 在堆上。
是的,您可以将 button
保留为 NON-pointer 会员。这是合法的。但这是不明智的。这种方法存在一个问题:您必须 #include
header 中的所有子部件,而不能 forward-declare 它们。这肯定会使您的编译时间更长。但这不是主要问题,您可以稍等片刻。更大的问题是您正在引入构建依赖项,这会降低您的代码的可组合性。想象一下,您将自定义小部件拆分为多个库 - 然后您 #include
在其他模块可见的 header 中的所有内容也需要对这些模块可见。这很快就会变得难以管理,因为您将无法 hide/encapsulate 实现细节。您的小部件使用某些按钮这一事实当然是一个实现细节。在 header 中保留尽可能少的细节。保留一个指针(可以是 forward-declared)肯定比保留一个 non-pointer 成员(必须是 #include
d!)负担更小。此外,如果你保留一个指针,那么你可以使用替换原则,这个指针实际上可以指向任何 subclass 实例。在小玩具应用程序或学校作业中,这无关紧要,但当您设计一个非常大的应用程序时,它包含许多模块和子模块,这些模块和子模块被组织为库,或者当您正在编写供其他人使用的库时,这些事情真的很重要。这使得可维护性和完全不可维护的代码之间存在差异。
在我的实践中,我只遇到过两个实际在堆栈上分配小部件的用例。首先是在 main()
中创建它们时,您可以在其中编写:
int main() {
QApplication app;
MainWindow w; // it is really on the stack
w.show();
return app.exec();
// here is the main window automatically destroyed
}
另一种情况是当您使用 exec()
.
打开一个带有阻塞事件循环的模态对话框时
void showSomeMessage() {
QMessageBox box; // yes, we should set a parent here, for sake of simplicity I omitted it
// etc. set text and button here
box.exec(); // blocking here, we are waiting for user interaction
// end of scope - we are done here, we do not need the message box any more
// so it is perfectly fine it gets automatically deleted here
}
这些案例都很好。实际上,正如您在这两种情况下看到的阻塞 exec()
,它们非常相似。
结论:最好通过指针声明成员 QObject
s 或 QWidget
s 并且总是 forward-declare 它们在你的 headers 而不是 #include
ing 他们的 headers。通过这种方式,您可以设计出更具可组合性、可维护性和 future-proof 的代码。您不会在小型玩具项目中看到任何显着差异,但是当您的代码库变大时,您将受益于这种良好做法。更好的习惯是通过 QPointer
(一个特殊的 Qt 弱指针)来保存它们,它有两个好处:1)它会自动初始化为 nullptr
和 2)当 object 时被销毁时,指针会自动设置为 nullptr
,因此您可以轻松地检查它是否还活着。另外 QPointer
提供到原始指针的隐式转换,因此它不会影响您编写代码的方式。这是一个非常方便的 class.
那里。我从 qt 看到了这个例子:https://doc.qt.io/qt-5/qtwidgets-widgets-calculator-example.html,想知道为什么 qt 总是用 new 创建小部件而不是在堆栈上分配
Button *Calculator::createButton(const QString &text, const char *member)
{
Button *button = new Button(text);
connect(button, SIGNAL(clicked()), this, member);
return button;
}
在堆栈上创建小部件更快,在这个问题中:
这很简单:因为您希望小部件即使在此功能范围完成后仍然存在并且 QObject
s 不可复制。以下代码不起作用,因为您无法复制 object.
Button Calculator::createButton(const QString &text)
{
Button button(text); // allocated on stack, it is OK so far...
return button; // nope, you cannot copy that! it does not compile.
}
以下代码也不起作用,因为您将传递已删除的 object。
Button *Calculator::createButton(const QString &text)
{
Button button(text); // allocated on stack, it is OK so far...
return &button; // ... returning pointer, still OK...
// Oh no! End of scope here, the object gets murdered now.
// whoever holds the pointer to it, holds a dead, invalid object.
}
object 在范围末尾被删除,您将传递一个指向已释放的指针,因此内存无效。
你提到的例子
class Widget2 : public QWidget
{
Q_OBJECT
public:
Widget2(QWidget* parent = nullptr);
private:
QPushButton button;
};
并不一定意味着 button
是在堆栈上创建的。这取决于 Widget2
的实例是如何创建的。如果它在堆栈上,那么 button
也在堆栈上。如果它在堆上 new
ed,那么 button
也在堆上。与声明 button
并将其保留为指针的“正常”情况的不同之处在于,在上面的示例中,它们在一次分配中一起分配。是的,它的速度可以忽略不计,但这只是一个过早的优化。小部件的分配永远不会成为您的瓶颈,相信我。顺便提一句。 Qt 中的大多数 objects 都是使用 PIMPL idiom 来实现的,以保持二进制兼容性,所以它们在内部分配他们私有的 objects 在堆上。
是的,您可以将 button
保留为 NON-pointer 会员。这是合法的。但这是不明智的。这种方法存在一个问题:您必须 #include
header 中的所有子部件,而不能 forward-declare 它们。这肯定会使您的编译时间更长。但这不是主要问题,您可以稍等片刻。更大的问题是您正在引入构建依赖项,这会降低您的代码的可组合性。想象一下,您将自定义小部件拆分为多个库 - 然后您 #include
在其他模块可见的 header 中的所有内容也需要对这些模块可见。这很快就会变得难以管理,因为您将无法 hide/encapsulate 实现细节。您的小部件使用某些按钮这一事实当然是一个实现细节。在 header 中保留尽可能少的细节。保留一个指针(可以是 forward-declared)肯定比保留一个 non-pointer 成员(必须是 #include
d!)负担更小。此外,如果你保留一个指针,那么你可以使用替换原则,这个指针实际上可以指向任何 subclass 实例。在小玩具应用程序或学校作业中,这无关紧要,但当您设计一个非常大的应用程序时,它包含许多模块和子模块,这些模块和子模块被组织为库,或者当您正在编写供其他人使用的库时,这些事情真的很重要。这使得可维护性和完全不可维护的代码之间存在差异。
在我的实践中,我只遇到过两个实际在堆栈上分配小部件的用例。首先是在 main()
中创建它们时,您可以在其中编写:
int main() {
QApplication app;
MainWindow w; // it is really on the stack
w.show();
return app.exec();
// here is the main window automatically destroyed
}
另一种情况是当您使用 exec()
.
void showSomeMessage() {
QMessageBox box; // yes, we should set a parent here, for sake of simplicity I omitted it
// etc. set text and button here
box.exec(); // blocking here, we are waiting for user interaction
// end of scope - we are done here, we do not need the message box any more
// so it is perfectly fine it gets automatically deleted here
}
这些案例都很好。实际上,正如您在这两种情况下看到的阻塞 exec()
,它们非常相似。
结论:最好通过指针声明成员 QObject
s 或 QWidget
s 并且总是 forward-declare 它们在你的 headers 而不是 #include
ing 他们的 headers。通过这种方式,您可以设计出更具可组合性、可维护性和 future-proof 的代码。您不会在小型玩具项目中看到任何显着差异,但是当您的代码库变大时,您将受益于这种良好做法。更好的习惯是通过 QPointer
(一个特殊的 Qt 弱指针)来保存它们,它有两个好处:1)它会自动初始化为 nullptr
和 2)当 object 时被销毁时,指针会自动设置为 nullptr
,因此您可以轻松地检查它是否还活着。另外 QPointer
提供到原始指针的隐式转换,因此它不会影响您编写代码的方式。这是一个非常方便的 class.