Компания ALT Linux
Опубликован: 07.03.2015 | Доступ: свободный | Студентов: 2195 / 528 | Длительность: 24:14:00
Лекция 14:

Собственные классы в Qt. Создание элементов графического интерфейса

14.2 Управление памятью. Иерархии объектов

Как мы уже знаем, каждый объект может иметь "родительские" и "дочерние" объекты. Таким образом объекты организуют в иерархические структуры напоминающие дерево. Каждый из объектов может содержать дочерние объекты, а также иметь родительский объект. Отношение между объектами можно задать либо воспользовавшись параметром конструктора (при создании), или же с помощью метода QObject::setParent(). Обычно пользуются первым способом, то есть задают родительский объект при конструировании.

//Задаём родительский виджет с помощью конструктора
QLabel * lLabel = new QLabel ( lSomeWidget );
	//Задаём родительский виджет с помощью метода QObject::setparent ( )
	QPushButton * lPushButton = new QPushButton;
	lPushButton->setParent ( lSomeWidget );

Если виджет содержит компоновщики, то и компоновщики и все размещённые в нём виджеты автоматически получат "родителя" (ведь каждый визуальный элемент наследует от QWidget, а тот в свою очередь наследует от QObject). Объекты также могут менять "родителя", для этого достаточно вызвать метод QObject::setParent (QObject *) с указателем на другой родительский объект. Объектную иерархию можно увидеть наглядно, воспользовавшись методом dumpObjectTree() класса QObject. Он выводит в стандартный поток вывода тип и имя всех дочерних объектов. Зададим имена для объектов lLabel и lPushButton:

lLabel ->setobjectName ( " childLabel " );
lPushButton->setobjectName ( " child PushButton " );

Если теперь вызвать метод dumpObjectTree() для объекта lSomeWidget, то получим вывод:

SomeWidget : :
	QLabel::childLabel
	QPushButton::childPushButton

Когда родительский объект удаляют, дочерние объекты тоже будут удалены из памяти перед удалением родительского. То же самое происходит и при удалении виджетов — все дочерние виджеты в визуальной иерархии тоже удаляются. Это позволяет управлять высвобождением памяти в программе.

Управление памятью важно, поскольку неуправляемое выделение памяти приводит к утечке памяти в программе — ситуации, когда программа не освобождает память. Если такое неуправляемое выделение памяти повторяется периодически, а сама программа выполняется относительно долго, то со временем программа будет занимать всё больше места в оперативной памяти. Наконец, таким образом можно исчерпать всю доступную память, что приведёт к аварийному завершению программы и исчерпанию ресурсов операционной системы.

Например, при использовании ключевого слова new память выделяется в динамически распределяемой памяти (так называемой "куче" — heap). Динамическая память должна быть освобождена от созданных объектов с помощью ключевого слова delete для того, чтобы избежать утечки памяти (memory leak). Объекты, созданные в динамически распределяемой памяти могут существовать столько, сколько необходимо. Динамическая память не будет освобождена автоматически, поэтому программист должен самостоятельно следить за высвобождением выделенной памяти. Рассмотрим следующий пример:

#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
int main ( int arg c, char * argv [ ] )
{
	QApplication lApplication ( arg c, argv );
	//(1) Память в динамически-распределяемой памяти (heap)
	QWidget _lSomeWidget = new QWidget ( 0 ); //Окно
	//Задаём родительский виджет с помощью конструктора
	QLabel * lLabel = new QLabel ( lSomeWidget );
	//Задаём родительский виджет с помощью метода QObject::setparent ( )
	QPushButton _ lPushButton = new QPushButton;
	lPushButton->setParent ( lSomeWidget );
	lLabel ->setText ( " Label " );
	lLabel ->move ( 10, 10 );
	lPushButton->setText ( " Button " );
	lPushButton->move ( 50, 10 );
	//(1) Память в динамически-распределяемой памяти (heap)
	lSomeWidget->resize ( 150, 50 );
	lSomeWidget->show ( );
return lApplication.exec ( );
}

Хотя с первого взгляда программа может показаться корректной (она компилируется и выполняется), в ней есть ошибка, связанная с некорректным управлением памятью. Для того, чтобы проанализировать использование памяти в программе, воспользуемся программой Valgrind. Это свободно-распространяемая программа для Linux с открытым кодом. Среда Qt Creator поддерживает работу с этой программой для анализа использования памяти и оптимизации программ на Qt.

Перейдите в режим анализа Analyse (Анализ) воспользовавшись кнопкой на панели переключения режимов работы слева или воспользуйтесь комбинацией клавиш Ctrl+6. Снизу под окном редактирования появится дополнительная панель. В выпадающем списке выберите Valgrind Memory Analyzer (Анализатор памяти Valgrind) и запустите процесс анализа (нажмите на кнопку с пиктограммой треугольника на панели или выберите в главном меню Analyze->Valgrind Memory Analyzer (Анализ->Анализатор памяти Valgrind). Начнётся анализ работы программы и программа запустится. Затем закройте окно программы. После завершения работы на панели появится сообщение об утечке памяти (см. рис. 14.1).

Сообщение указывает на строку:

QWidget _lSomeWidget = new QWidget ( 0 ); //Окно
Анализ памяти с помощью Valgrind.

увеличить изображение
Рис. 14.1. Анализ памяти с помощью Valgrind.

Это происходит из-за того, что для lSomeWidget была выделена динамическая память, но не была корректно высвобождена с помощью оператора delete. Для того, чтобы корректно освободить память, модифицируем последние строки программы:

int exitCode = lApplication.exec ( );
//(1) Память в динамически-распределяемой памяти (heap)
delete lSomeWidget;
return exitCode;

Скомпилируем программу — после повторного анализа сообщение об утечке памяти исчезнет. Конечно, в этом примере утечка памяти не приводит к негативным последствиям. Выделение памяти происходит только раз и после завершения работы вся использованная оперативная память высвобождается операционной системой. Но в крупных проектах утечки памяти могут стать серьёзной проблемой. Инструменты для анализа памяти, такие как Valgrind, позволяют локализовать и исправить их.

Можно выделить память для родительского объекта в стеке. Память для объектов, созданных в стеке, освобождается автоматически как только объект выходит за пределы области видимости. Поэтому объекты, созданные в стеке, удаляются как только они выходят из области видимости. Для того, чтобы освободить память автоматически, достаточно только родительский объект создать в стеке. Как только родительский объект удаляется, будут автоматически удалены и все дочерние объекты. Мы можем модифицировать последний пример таким образом, чтобы память для родительского объекта была выделена в стеке. Для этого закомментируйте все части программы обозначенные (1) и измените текст программы:

//(2) Память в стеке
SomeWidget lSomeWidget;
//Задаём родительский виджет с помощью конструктора
QLabel * lLabel = new QLabel(& lSomeWidget );
//Задаём родительский виджет с помощью метода QObject::setparent ( )
QPushButton * lPushButton = new QPushButton;
lPushButton->setParent (& lSomeWidget );
.....
//(2) Память в стеке
lSomeWidget.resize ( 1 5 0, 5 0 );
lSomeWidget.show ( );

При описании собственных классов необходимо учитывать следующие рекомендации:

  • задавать для конструкторов класса параметр, который принимает указатель на родительский объект и имеет значение по умолчанию 0;
  • создавать дополнительный конструктор, который принимает только параметр с указателем на родительский объект;
  • параметр с указателем на "родителя" желательно должен быть первым параметром среди параметров со значением по умолчанию (если параметров со значением по умолчанию несколько);
  • используйте ключевое слово this как указатель на родительский объект при создании объектов внутри своего класса. Например:
    #include <QWidget>
    class CustomWidget : public QWidget
    {
    	Q_OBJECT
    public :
    	explicit CustomWidget ( QWidget * parent = 0 );
    };
    //Конструктор
    CustomWidget::CustomWidget ( QWidget * parent ) : QWidget ( parent )
    {
    	//Задаём родительский виджет — this то есть экземпляр класса CustomWidget
    	QPushButton * lPushButton = new QPushButton ( this );
    	lPushButton->setGeometry ( 50, 50, 200, 30 );
    }
Сергей Радыгин
Сергей Радыгин

Символы кириллицы выводит некорректно. Как сделать чтобы выводился читабельный текст на русском языке?

Тип приложения - не Qt,

Qt Creator 4.5.0 основан на Qt 5.10.0. Win7.

 

Юрий Герко
Юрий Герко

Кому удалось собрать пример из раздела 13.2 Компоновка (Layouts)? Если создавать проект по изложенному алгоритму, автоматически не создается  файл mainwindow.cpp. Если создавать этот файл вручную и добавлять в проект, сборка не получается - компилятор сообщает об отсутствии класса MainWindow. Как правильно выполнить пример?