关闭应用程序时 QQuickItem 析构函数/changeListeners 崩溃(Qt 5.6)

Posted

技术标签:

【中文标题】关闭应用程序时 QQuickItem 析构函数/changeListeners 崩溃(Qt 5.6)【英文标题】:Crash in QQuickItem destructor / changeListeners when closing application (Qt 5.6) 【发布时间】:2016-06-07 18:26:40 【问题描述】:

我们有一个相当大的 QtQuick 应用程序,有很多模式对话框。所有这些模式共享一致的外观和行为,并具有 leftButtons、rightButtons、内容和其他警告小部件。我们使用以下基类(PFDialog.qml):

Window 
    property alias content: contentLayout.children
    ColumnLayout 
        id: contentLayout
    

并以下列方式声明对话框(main.qml):

Window 
    visible: true
    property var window: PFDialog 
        content: Text  text: "Foobar" 
    

问题是当应用程序关闭时,QQuickItem 析构函数中会发生段错误。这个段错误很难重现,但这是实现它的可靠方法:在调试模式下的 Visual Studio 中,释放的内存被 0xDDDDDDD 填充,每次都会触发段错误。

完整的示例应用程序可以在这里找到:https://github.com/wesen/testWindowCrash

崩溃发生在QQuickItem::~QQuickItem:

for (int ii = 0; ii < d->changeListeners.count(); ++ii) 
    QQuickAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate();
    if (anchor)
        anchor->clearItem(this);

原因是我们对话框的内容(上例中的 Text 项)是主窗口的 QObject 子窗口,但对话框窗口的可视子窗口。关闭应用程序时,对话框窗口首先被销毁,而在Text项被删除时,对话框窗口(仍然注册为changeListener)是陈旧的。

现在我的问题是:

这是一个 QtQuick 错误吗?对话框是否应在销毁时将其自身注销为其子级的 changeListener(我认为应该) 我们的property alias content: layout.children 模式是否正确,或者有更好的方法吗?声明默认属性别名时也会发生这种情况。

为了完整起见,以下是我们在应用程序中对其进行修补的方法。当内容更改时,我们将所有项目重新设置为布局项目。 A 优雅,你都会同意的。

function reparentTo(objects, newParent) 
    for (var i = 0; i < objects.length; i++) 
        qmlHelpers.qml_SetQObjectParent(objects[i], newParent)
    

onContentChanged: reparentTo(content, contentLayout)

【问题讨论】:

你能用一个独立的测试用例编辑这个问题吗? 对不起,我不确定我是否在关注你。这几乎是我所能做到的自包含(顶部的 8 行是整个应用程序)。如果你想要开箱即用的东西,你可以克隆存储库。 哦,好的,抱歉,我没听清这一切。谢谢! no pb,这有点令人困惑,因为它需要分成两个文件才能显示行为。 【参考方案1】:

我遇到过很多次这个问题,我不认为这是一个错误,更像是一个设计限制。您获得的隐含行为越多,您拥有的控制权就越少,从而导致对象销毁和访问悬空引用的不适当顺序。

在许多情况下,当您超出“按书本”的琐碎 qml 应用程序的界限时,这可能会“自行”发生,但在您的情况下,是您在做这件事。

如果您想要适当的所有权,请不要使用:

property var window: PFDialog 
    content: Text  text: "Foobar" 

改用这个:

property Window window: dlg // if you need to access it externally
PFDialog 
    id: dlg
    content: Text  text: "Foobar" 

这是一个很好的理由:

property var item : Item 
  Item 
    Component.onCompleted: console.log(parent) // qml: QQuickItem(0x4ed720) - OK
  

// vs
property var item : Item 
  property var i: Item 
    Component.onCompleted: console.log(parent) // qml: null - BAD
  

子项与属性不同。仍会收集属性,但它们不是父级。

关于实现“动态内容”的东西,我用ObjectModel取得了不错的成绩:

Window  
    property ObjectModel layout
    ListView             
        width: contentItem.childrenRect.width // expand to content size
        height: contentItem.childrenRect.height
        model: layout
        interactive: false // don't flick
        orientation: ListView.Vertical
    

然后:

PFDialog 
    layout: ObjectModel 
        Text  text: "Foobar" 
        // other stuff
    

最后,为了在关闭应用程序之前进行显式清理,您可以在主 QML 文件上实现一个处理程序:

onClosing: 
    if (!canExit) doCleanup()
    close.accepted = true

这样可以确保在没有先进行清理的情况下不会破坏窗口。

最后:

是我们的属性别名内容:layout.children 模式正确,或者是 有更好的方法吗?这也发生在声明一个 默认属性别名。

这不是我最后一次调查它,但至少是几年前的事了。将对象声明为子对象实际上成为其他对象的子对象当然很好,但在当时这是不可能的,而且可能仍然不可能。因此需要涉及对象模型和列表视图的稍微更详细的解决方案。如果您调查此事并发现不同之处,请发表评论让我知道。

【讨论】:

【参考方案2】:

我相信您不能在 var 中声明 Window 对象。在我的测试中,子窗口永远不会打开,有时在启动时会损坏。

一个窗口可以被声明在一个Item中,也可以在另一个Window中;在这种情况下,内部窗口将自动成为外部窗口的“瞬态” 见:http://doc.qt.io/qt-5/qml-qtquick-window-window.html

将您的代码修改为:

Window 
    visible: true
    PFDialog 
        content: Text  text: "Foobar" 
    

【讨论】:

是的,这是我们想要的行为,实际上我们在主应用程序中使用 Dialog 作为一个类。我认为这不是这里的问题,确实你可以用一个简单的 Item 替换 Window,我只是想模仿我们的主应用程序。感谢您的洞察力。

以上是关于关闭应用程序时 QQuickItem 析构函数/changeListeners 崩溃(Qt 5.6)的主要内容,如果未能解决你的问题,请参考以下文章

c++ 析构函数 是在啥时候执行

2-Qt关闭子窗口时执行特定代码

访问冲突 - 为啥基类析构函数被调用两次? [关闭]

如何在关闭 Qt 控制台应用程序之前运行我的析构函数?

MySQLi 在析构函数中立即关闭

Qt窗口关闭和应用程序停止是否调用析构函数的一些说明