Qt 5.7 не возвращает ресурсы при уничтожении родительского виджета

Я использую Qt 5.7 с C++ в Visual Studio Community 2015. Что я знаю об управлении ресурсами Qt, так это то, что когда вы убиваете родителя, вам не нужно уничтожать дочерние указатели на этот родительский объект. Однако, когда я пытался, я не получил результатов, указывающих на это направление, и я не понимаю, почему.

Я запускаю код ниже, чтобы получить контрольную точку. Обратите внимание на закомментированный блок кода:

#include <QtWidgets/QApplication>
#include <QWidget>
#include <QPushButton>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget* w0 = new QWidget;
    /*
    QWidget* w1 = new QWidget;
    w1->setWindowTitle("Window 1");
    for(size_t i = 0; i < 1000; i++) {
        QPushButton* pb = new QPushButton(w1);
    }
    w1->show();
    */
    w0->show();
    return a.exec();
}

При выполнении этого кода VS говорит, что память процесса составляет 4 МБ.

введите здесь описание изображения

И затем я запускаю код ниже, чтобы использовать больше памяти. Тот же код без блока комментариев:

#include <QtWidgets/QApplication>
#include <QWidget>
#include <QPushButton>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget* w0 = new QWidget;

    QWidget* w1 = new QWidget;
    w1->setWindowTitle("Window 1");
    for(size_t i = 0; i < 1000; i++) {
        QPushButton* pb = new QPushButton(w1);
    }
    w1->show();

    w0->show();
    return a.exec();
}

На этот раз он использовал 9 МБ памяти.

введите здесь описание изображения

Все идет нормально. Теперь я ожидаю, что когда я уничтожу w1, он должен вернуть ресурсы, используемые его дочерними объектами (кнопками), и я должен увидеть уменьшение используемой памяти. Но этого не происходит. Я убиваю w1, и w0 все еще работает, поэтому я все еще могу наблюдать за использованием памяти, w0 не является родителем w1, все кнопки являются дочерними для w1, но память не возвращается. Что я делаю/понимаю неправильно?

Обновление: в приведенных выше примерах я просто закрываю (нажмите X вверху) окно, на которое указывает w1, я думаю, что он также удалит указатель w1, но для проверки я запустил приведенный ниже код, и этот код использует 6 МБ объем памяти. Так что видимо 3 МБ возвращается после добавления

delete w1;

Новый код ниже:

#include <QtWidgets/QApplication>
#include <QWidget>
#include <QPushButton>

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QWidget* w0 = new QWidget;

    QWidget* w1 = new QWidget;
    w1->setWindowTitle("Window 1");
    for(size_t i = 0; i < 1000; i++) {
        QPushButton* pb = new QPushButton(w1);
    }
    w1->show();

    w0->show();
    delete w1;

    return a.exec();
}

но все еще есть утечка 2 МБ. На данный момент два вопроса:

1. Почему я должен явно удалять w1? Почему закрыть окно недостаточно? 2. Почему утечка сохраняется даже после явного удаления w1?


person Deniz    schedule 16.12.2016    source источник
comment
Как именно вы убиваете w0 и w1? Вы не показываете это в своем коде.   -  person Mike    schedule 16.12.2016
comment
В приведенном выше примере я просто закрываю окно, на которое указывает w1, думая, что это также уничтожит указатель (не так ли?). Теперь я только что запустил тест и добавил delete w1; ниже w0->show();, и теперь используемая память составляет 6 МБ, что все еще не 4 МБ. Так что, видимо, да, есть разница, когда я сам удаляю указатель, но он все еще кажется утечкой, и я думаю, что закрытия окна должно быть достаточно, чтобы вернуть использованную память.   -  person Deniz    schedule 16.12.2016
comment
Нет, закрытие окна не удаляет, если вы не установили атрибут WA_DeleteOnClose . Вы установили это?   -  person Mike    schedule 16.12.2016
comment
@Mike Нет, нет, но я сделал новый тест с delete w1;, которого, я думаю, должно быть достаточно, чтобы получить 5 МБ, используемые w1. Но я все еще получаю утечку 2 МБ даже после этого удаления.   -  person Deniz    schedule 16.12.2016
comment
Примечание: вы можете просто #include <QtWidgets> без каких-либо других включений. Кроме того, форма #include <QtModule/QClass> неверна, так как скрывает неверную конфигурацию проекта до времени компоновки. Используйте #include <QClass>. Если компилятор жалуется на отсутствие включаемого файла, то вы знаете, что должны проверить файл .pro на наличие правильных модулей, а затем повторно запустить qmake в проекте.   -  person Kuba hasn't forgotten Monica    schedule 19.12.2016
comment
@KubaOber Спасибо за эту информацию. Эти включения на самом деле поступают из надстройки Qt для Visual Studio. Но я бы сам включил их так же, как я думаю, так что спасибо.   -  person Deniz    schedule 22.12.2016
comment
В конечном счете, ответственность за ваш код лежит на вас. Плагин может давать вам подсказки - он может даже давать вам неправильные подсказки. Вы должны понимать любой код, который дает вам третья сторона, прежде чем использовать его вслепую. Включая плагин Qt.   -  person Kuba hasn't forgotten Monica    schedule 22.12.2016
comment
@KubaOber ты прав. еще раз спасибо, что поделились со мной этой информацией.   -  person Deniz    schedule 22.12.2016


Ответы (2)


Нет необходимости, чтобы ваш диспетчер памяти операционной системы освобождал каждый освобожденный байт памяти кучи. Обычно это не так. Но это не значит, что память утекла.

Например, вы можете начать с 5 МБ использования памяти, затем выделить еще 5 МБ для объектов, а при освобождении освобождается только 3 МБ, и у вас остается использование 7 МБ. Но если вы снова выделите объекты на 5 МБ, использование памяти не подскочит до 12 МБ, оно все равно подскочит до 10, потому что эти 2 невысвобожденных МБ будут повторно использоваться.

Посмотрите здесь, где я исследовал один такой проблема. Как видите, какое-то время использование памяти продолжает расти, с 41 до 53 Мб, но затем стабилизируется и не растет дальше даже после сотен выделений/освобождений. Различные диспетчеры памяти ОС работают по-разному, общий объем памяти и свободная память также являются фактором, возможно, также собственными шаблонами использования приложения.

Это можно расценивать как утечку только в том случае, если каждый раз, когда вы это делаете, использование памяти увеличивается на 2 МБ и никогда не достигает стабильного уровня. Если вы создаете пользовательский виджет с отладочным сообщением в деструкторе, вы можете проверить, когда и уничтожаются ли он и его дочерние элементы. Но опять же, нет гарантии, что вы восстановите 100% используемой памяти.

person dtech    schedule 16.12.2016
comment
Это очень хороший момент, я собирался упомянуть об этом в своем ответе, но я не уверен, что VS2015 показывает на мониторе памяти ... он может показывать использование памяти с точки зрения ОС, и тогда вы полностью правы или теоретически это может показать правильное распределение через среду выполнения С++, опять же, теоретически на это не должно влиять выделение памяти из ОС. Но, поскольку я нахожусь в 2013 году, я все еще не могу это проверить. Но я бы проголосовал за ваш ответ =) - person evilruff; 16.12.2016
comment
Комментарии @evilruff и Майка были очень полезными, дав мне знать о флаге для уничтожения объекта при закрытии. Однако то, что действительно прояснило для меня, был ваш ответ, поэтому я устанавливаю его как принятый ответ. Я проголосовал за ответы Майка и злодея, потому что они также помогли мне. Спасибо всем троим. - person Deniz; 16.12.2016

Предположение, которое вы делаете, неверно, затем вы закрываете окно, которое просто скрывается сигналом закрытия. Вызова деструктора нет, пока вы не установите флаг Qt::WA_DeleteOnClose, тогда ваш виджет будет удален при закрытии.

person evilruff    schedule 16.12.2016
comment
Спасибо за эту информацию. Тем не менее, я обновил свой вопрос новым тестом, включающим явный delete w1;, и он возвращает 3 МБ обратно, но все еще есть утечка 2 МБ. Как мне интерпретировать эту ситуацию? - person Deniz; 16.12.2016
comment
Ну, внутри Qt происходит много внутренних инициализаций, тогда вы используете другой компонент, например, QPushButton. Он создает разные кешированные вещи, например, если вы собираетесь создать больше QPushButtons после этого.. Честно говоря, я бы не стал беспокоить.. если вы поместите цикл в свой код, скажем, с 10000 итераций, вы увидите, что все в порядке действительно случается - person evilruff; 16.12.2016
comment
Также многие вещи удаляются с помощью deleteLater(), поэтому это произойдет только после цикла событий. - person evilruff; 16.12.2016