Lecture

Компания ALT Linux
Опубликован: 07.03.2015 | Access: free | Students: 2209 / 537 | Длительность: 24:14:00
Lecture 14:

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

< Lecture 13 || Lecture 14: 1234567 || Lecture 15 >

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 );
    }
< Lecture 13 || Lecture 14: 1234567 || Lecture 15 >