Qt:子对象可以在其父对象中组合吗?
Posted
技术标签:
【中文标题】Qt:子对象可以在其父对象中组合吗?【英文标题】:Qt: Can child objects be composed in their parent object? 【发布时间】:2011-06-03 07:46:35 【问题描述】:在 Qt 中,我可以通过组合将子小部件嵌入其父小部件,还是必须使用 new
创建它们?
class MyWindow : public QMainWindow
...
private:
QPushButton myButton;
MyWindow::MyWindow ()
: mybutton("Do Something", this)
...
文档说任何从QObject
派生的对象都会在其父对象被销毁时自动销毁;这意味着调用delete
,在上面的例子中会崩溃。
我必须使用以下内容吗?
QPushButton* myButton;
myButton = new QPushButton("Do Something", this);
编辑
答案多种多样,基本上可以归结为三种可能性:
是的,构图没问题。 Qt 可以弄清楚对象是如何分配的,并且只有delete
堆分配的对象(这是如何工作的?)
是的,合成是可以的,但不要指定父级,否则父级会在对象上调用delete
(但是没有父级的小部件不会变成***窗口?)
否,小部件总是必须在堆上分配。
哪个是正确的?
【问题讨论】:
【参考方案1】:当特定对象的删除序列开始时,将删除非静态、非堆成员变量。只有当所有成员都被删除后,才会进入基类的析构函数。因此 QPushButton myButton 成员将在 ~QMainWindow() 被调用之前被删除。并且来自 QObject 文档:“如果我们在其父对象之前删除一个子对象,Qt 将自动从父对象的子对象列表中删除该对象”。因此不会发生崩溃。
【讨论】:
你的答案几乎是正确的,但是Destruction的顺序不正确。参见例如msdn.microsoft.com/en-us/library/8183zf3x%28v=vs.100%29.aspx【参考方案2】:Object trees & ownership 回答您的问题。基本上,当在堆上创建子对象时,它将被其父对象删除。
另一方面,当子对象在堆栈上创建时,销毁顺序很重要。子级将在其父级之前被销毁,并将其自身从其父级列表中删除,这样其析构函数就不会被调用两次。
该链接中还有一个示例显示了有问题的破坏顺序。
【讨论】:
所以,如果我们有(堆栈或堆)复合并且它被破坏(通过delete
或堆栈对象的生命周期结束时),首先调用复合的析构函数(并执行任何操作想要做,但不从其子代中删除任何东西,因为这个逻辑是在 QObject 中实现的),然后复合的子代/成员被破坏(调用它们的析构函数)并将自己从对象中仍然存在的部分中删除(再次使用 QObject功能),然后才最终调用 QObject 的析构函数,并且看不到任何子级 - 因此没有双重删除。【参考方案3】:
文档说任何从 QObject 派生的对象都会在其父对象被销毁时自动销毁; 这意味着调用删除
没有。它意味着调用该特定实体的析构函数。
在您的示例中,如果 MyWindow
被销毁,则意味着 MyWindow
的析构函数已被调用。这反过来会调用析构函数myButton
,它已经在QPushButton
中实现。
如果你有复合实体,只会在该实体上调用析构函数,而不是delete
,因此它不会崩溃。
Qt 中的父子关系不需要特别位于堆栈或堆中。它可以存在于任何东西中。
堆栈上的父子关系的类似示例超过here。
HTH..
【讨论】:
父母如何知道孩子是否是堆分配的?据我了解,调用析构函数不会释放对象占用的堆空间(需要delete
)——或者我错了吗?
@ Jen,是的。如果它是通过new
分配的,你必须delete
它。我假设应用程序能够通过 Qt 元对象系统识别孩子的分配..找不到任何关于此的文档。如果我发现了什么会更新..
@ Jen,但不会崩溃。你试过吗?它不会崩溃吧?
尝试delete
组合或堆栈分配的对象可能不会使应用程序崩溃,但可能会导致堆损坏和未定义的行为。
父母不知道孩子是否是堆分配的。但是 QObject 在其析构函数中将自己从其父级的子级列表中删除。【参考方案4】:
对象只有在有父指针时才会被销毁,所以你可以使用:
MyWindow::MyWindow ()
: mybutton("Do Something", 0)
...
【讨论】:
如果QWidget
没有父级,它不会变成***窗口吗?
是的,这是真的。如果您希望 QWidget 成为另一个 QWidget 的子级,则必须提供父级,这意味着您必须在堆上创建它。但是还有其他 QObject 派生类,将它们用作成员变量是有意义的。
-1:您的评论比您的实际答案更多。正如其他人所指出的那样,QObject
必须在堆上并没有任何暗示,因为它有一个父级。
您的反对票是不合理的。来自 QObject::~QObject() 的文档:“警告:所有子对象都被删除。如果这些对象中的任何一个在堆栈或全局上,您的程序迟早会崩溃。”堆对象只有在保证子的析构函数在父析构函数之前完成时才起作用(参见 OrcunC 答案中的链接)。【参考方案5】:
你应该在堆上创建它,因为 QObject 会破坏它:
class MyWindow : public QMainWindow
...
private:
QPushButton *myButton;
MyWindow::MyWindow ()
: mybutton( new QPushButton( "Do Something", this) )
...
【讨论】:
-1:正如其他人指出的那样,没有必要在堆上创建它。【参考方案6】:调用delete
运算符不会使你的应用程序崩溃,您可以阅读以下引用
Qt 的父子机制在 QObject 中实现。当我们使用父对象创建对象(小部件、验证器或任何其他类型)时,父对象会将对象添加到其子对象列表中。当父级被删除时,它会遍历其子级列表并删除每个子级。然后孩子们自己删除他们所有的孩子,以此类推,直到没有留下。父子机制极大地简化了内存管理,降低了内存泄漏的风险。我们必须调用 delete 的唯一对象是我们使用 new 创建且没有父对象的对象。如果我们在父对象之前删除一个子对象,Qt 会自动从父对象的子对象列表中删除该对象。
注意父参数默认为NULL
(默认参数)
这是 QPushButton 构造函数
QPushButton ( const QString & text, QWidget * parent = 0 )
所以你可以使用
MyWindow::MyWindow () : mybutton( new QPushButton( "Do Something") ) ...
您可以随时在任何组件上调用delete
Qt 会注意这一点
【讨论】:
【参考方案7】:让我在这里quote the source。
816 QObject::~QObject()
817
818 Q_D(QObject);
819 d->wasDeleted = true;
820 d->blockSig = 0; // unblock signals so we always emit destroyed()
821
...
924
925 if (!d->children.isEmpty())
926 d->deleteChildren();
927
928 qt_removeObject(this);
929
930 if (d->parent) // remove it from parent object
931 d->setParent_helper(0);
932
933 #ifdef QT_JAMBI_BUILD
934 if (d->inEventHandler)
935 qWarning("QObject: Do not delete object, '%s', during its event handler!",
936 objectName().isNull() ? "unnamed" : qPrintable(objectName()));
937
938 #endif
939
...
1897 void QObjectPrivate::deleteChildren()
1898
1899 const bool reallyWasDeleted = wasDeleted;
1900 wasDeleted = true;
1901 // delete children objects
1902 // don't use qDeleteAll as the destructor of the child might
1903 // delete siblings
1904 for (int i = 0; i < children.count(); ++i)
1905 currentChildBeingDeleted = children.at(i);
1906 children[i] = 0;
1907 delete currentChildBeingDeleted;
1908
1909 children.clear();
1910 currentChildBeingDeleted = 0;
1911 wasDeleted = reallyWasDeleted;
1912
如您所见,QObject
确实在析构函数中执行了 delete
的每个子级。另外,析构函数是执行before任意成员的析构函数;因此,如果所讨论的组合等于父级 - 那么成员 QObject
将没有任何机会将自己从其父级的子级列表中删除。
很遗憾,这意味着您不能将QObject
写入其父级。但是您可以组合成其他对象,也可以在堆栈上分配——只要您保证在父对象开始销毁之前将其父对象重置为 0 或将其父对象重置为 0。
【讨论】:
以上是关于Qt:子对象可以在其父对象中组合吗?的主要内容,如果未能解决你的问题,请参考以下文章