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:子对象可以在其父对象中组合吗?的主要内容,如果未能解决你的问题,请参考以下文章

7.QT-Qt对象间的父子关系

QT窗口组件的父子关系

Qt内存回收(转载)

QT 使用 QT Designer 调整布局大小?

Qt中的内存回收机制

Qt 对象清理器