Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Right way to delete resources



  • I'm not sure if I'm doing it correcly.
    In a QMainWindow application I create and populate tabs for a QWidgetTab at runtime. Then I need to free the resources to create a new set.

    Here how I create them:

    void MainWindow::createTabs()
    {
         QStringList names; // contains the list of the tab's name
        foreach (QString name, names)
        {
            QWidget *widget = new QWidget();
            QScrollArea *scrollArea = new QScrollArea();
            scrollArea->setWidget(widget);
            loadTab(view, widget, name);
            ui->tabView->addTab(scrollArea, name);
        }
    }
    
    void MainWindow::loadTab(MainWindow::Views view, QWidget *widget, QString tab)
    {
        FlowLayout *layout = new FlowLayout();
        layout->setOrientation(Qt::Vertical);
        widget->setLayout(layout);
        populateTab(view, tab, widget);
    }
    
    void MainWindow::populateTab(MainWindow::Views view, QString tab, QWidget *parent)
    {    
        QMutableMapIterator<int, DatabaseManager::Data> i(_mapData[tab]); // contains the information to display controls
        while (i.hasNext())
        {
            i.next();
            DatabaseManager::Data data = i.value();
    
            QGroupBox *group = retrieveGroup(data.group, tab, parent);
            QFormLayout *formLayout = qobject_cast<QFormLayout *>(group->layout());
    
            QLineEdit *widget = new QLineEdit();
            data.widget = widget;
            i.setValue(data);
            QLabel *label = new QLabel(data.description);
            formLayout->addRow(label, data.widget);
        }
    }
    
    QGroupBox *MainWindow::retrieveGroup(QString name, QString tab, QWidget *parent)
    {
        if (_mapGroups[tab].contains(name)) return _mapGroups[tab][name];
    
        QGroupBox *group = new QGroupBox(name, parent);
    
        QFormLayout *formLayout = new QFormLayout();
        group->setLayout(formLayout);
        parent->layout()->addWidget(group);
        _mapGroups[tab][name] = group;
        return group;
    }
    

    And here how I'm trying to free resources:

    for (int i = 0; i < ui->tabView->count(); i++)
    {
        qDeleteAll(ui->tabView->widget(0)->findChildren<QWidget *>("", Qt::FindDirectChildrenOnly));
        delete ui->tabView->widget(0);
    }
    ui->tabView->clear();
    _mapGroups.clear();
    

    But I see a memory leakage: every time I load the very same set of tabs the memory occupation of my process increases.
    I tried to change this:

        qDeleteAll(ui->tabView->widget(0)->findChildren<QWidget *>("", Qt::FindDirectChildrenOnly));
    

    to

        qDeleteAll(ui->tabView->widget(0)->findChildren<QWidget *>("", Qt::FindChildrenRecursively));
    

    but I got a crash in Qt code.
    How would you delete all the newly crated objects?



  • @Mark81
    I believe you have two problems here....

    1. When you parent a widget like a tab to a TabWidget, when the tabwidget goes out of scope, it's destructor is called. If you're trying to delete
      the tab from the tabwidget, it would be a double delete because when setlayout is called, it automatically makes the children tabs parented to the tabwidget which autmatically
      "deletes" its children when it goes out of scope.
    2. That kind of leads into 2... that means if that tabwidget exists for the lifetime of the program, you will see your "memory go up." even though you removed the widget etc.
      To free memory in the middle of the program I think you would need to unparent the child widget... then keep a pointer to the address then call delete.

    I may be wrong because I am a newbie so take what I say with a grain of salt.

    Also, a quick answer from a google search explains point 2 a bit further.

    "It is not necessary that your operating system memory manager will release each and every deallocated heap memory byte. Usually it does not. But that doesn't mean that the memory has leaked.

    For example, you may begin with 5 mb of memory usage, then allocate another 5 mb worth of objects, and when deallocated it only frees 3 mb and you are left with 7 mb usage. But if you allocate the 5 mb worth of objects again, memory usage will not jump to 12 mb, it will still jump to 10, because those 2 unreleased mb will be reused.

    Take a look here, where I investigated one such issue. As you can see, for a while the memory usage keeps growing, from 41 to 53 mb, but then it stabilizes and it doesn't grow further even after hundreds of allocations/deallocations. Different OS memory managers work in a different way, the amount total memory and free memory are also a factor, possibly the application's own usage patterns as well.

    It can be regarded as a leak only if each time you do that the memory usage increases by 2 mb and never reaches a stable level. If you make a custom widget with a debug message in the destructor, you can verify when and whether it and its children are being destroyed. But again, there is no guarantee that you reclaim 100% of the used memory."



  • @JahJerMar Thanks a lot for your answer.
    Some more details:

    • I do have a memory leak: every time I allocate the same set of tabs the memory usage increase of about 2.1 MB and going on with free resources and allocate again it reaches hundreds of MB.

    • About QTabWidget: I'm not sure to understand. I know that when I remove a tab it does not delete the content, this is why I call qDeleteAll and then delete the widget. But do this two commands ensure that all the children objects are deleted as well?

    • When my tabs were populated with simpler widgets (i.e. just a QWidget, a QVBoxLayout and plenty of QLabels) the code above worked and the memory usage didn't grow.

    I'm afraid about all the nested objects I create:

    • many QScrollArea() as tabs
    • a QWidget() as area's tab
    • a FlowLayout() as widget's layout
    • many QGroupBox(widget) in the layout
    • a QFormLayout() as group's layout
    • many QLineEdit () in the layout

    My code pretends to delete all the above items, cycling among each tab and deleting the "direct children" and the tab itself (the QScrollArea object). I've already tried to add a debug line in the destructor of FlowLayout: it is actually called, though less times as I expect (i.e. I have 16 tabs, hence 16 FlowLayout but I get only 8 debug messages).

    Because all of them are inside layouts and the layouts are assigned to widgets, deleting the widgets does not delete also their layouts and the contained objects?



  • Something you. I was confused by my code revision and I forgot a line in the code:

    QLineEdit *widget = new QLineEdit();
    data.widget = widget;
    i.setValue(data);
    

    this save the pointer to the newly created widget into my Data struct.
    Actually, after deleting the resources those pointers are still valid! I mean they still points to the widgets - I bet here is the problem.
    But adding this before the code already posted above:

    QMapIterator<QString, QMap<int, DatabaseManager::Data>> i1(_mapData);
    while (i1.hasNext())
    {
        i1.next();
        QMapIterator<int, DatabaseManager::Data> i2(i1.value());
        while (i2.hasNext())
        {
            i2.next();
            delete i2.value().widget; // this is the pointer to the created widgets!
        }
    }
    

    leads to a segmentation fault on the delete call.



  • With this code the memory usage increases now of 40-50 kB each time (instead of 2.1 MB):

    QMutableMapIterator<QString, QMap<int, DatabaseManager::Data>> i1(_mapData);
    while (i1.hasNext())
    {
        i1.next();
        QMutableMapIterator<int, DatabaseManager::Data> i2(i1.value());
        while (i2.hasNext())
        {
            i2.next();
            if (i2.value().widget)
            {
                DatabaseManager::Data data = i2.value();
                data.widget->deleteLater();
                data.widget = nullptr;
                i2.setValue(data);
            }
        }
    }
    
    QMutableMapIterator<QString, QMap<QString, QGroupBox *>> i3(_mapGroups);
    while (i3.hasNext())
    {
        i3.next();
        QMutableMapIterator<QString, QGroupBox *> i4(i3.value());
        while (i4.hasNext())
        {
            i4.next();
            qDeleteAll(i4.value()->findChildren<QWidget *>("", Qt::FindDirectChildrenOnly));
            i4.value()->deleteLater();
        }
    }
    
    for (int i = 0; i < ui->tabView->count(); i++)
    {
        QScrollArea *a = qobject_cast<QScrollArea *>(ui->tabView->widget(0));
        qDeleteAll(a->widget()->findChildren<QWidget *>("", Qt::FindDirectChildrenOnly));
        qDeleteAll(ui->tabView->widget(0)->findChildren<QWidget *>("", Qt::FindDirectChildrenOnly));
        delete ui->tabView->widget(0);
    }
    ui->tabView->clear();

  • Qt Champions 2019

    @Mark81 said in Right way to delete resources:

    i4.value()->findChildren<QWidget *>("", Qt::FindDirectChildrenOnly)

    Are you sure this actually finds anything?


Log in to reply