Основы Управления Памятью
November 18th, 2008 Begemot Posted in Учебники
В связи глобальной расхлебаностью и, как следствие, черепашьими темпами перевода мной книги. Родилась мысль переводить и выкладывать маленькими кусочками, все таки мелкие проекты гораздо легче довести. Итак первая часть 15 главы.
Глава 15. Управление памятью, отладка приложения и проверка ошибок
Отслеживание ошибок одна из важнейших частей процесса разработки программы. Эта глава описывает возможности, предоставляемые библиотекой, для обнаружения проблем с памятью. Средства, способствующие использованию, так называемого “защитного программирования” – обнаружения проблем так рано как только возможно, все это делает отладку программы значительно более легким делом, а само приложение более надежным. Мы также объясним когда стоит создавать объект в куче, а когда можно использовать стек. Мы обсудим как использовать информацию о типе времени выполнения, использование механизма модулей, и поддержку библиотекой исключений С++. В конце главы мы дадим несколько общих советов касающихся отладки.
Основы Управления Памятью
Как обычно при программировании на С++, вы можете создавать объекты либо в стеке, либо в куче используя оператор new. Объект, созданный в стеке, доступен только в приделах своей области видимости. В момент выхода из нее будет вызван деструктор объекта, после чего он просто перестанет существовать. С другой стороны, объект, созданный в куче, будет доступен до тех пор пока вы явно не удалите его оператором delete или программа не завершится.
Создание и удаление Оконного объекта (Window object)
Как правило, вы создаете оконный объект, например wxFrame или wxButton, в куче используя оператор new. Обычно он должен существовать неопределенное время, до тех пор пока пользователь не решит закрыть окно. Учтите, что wxWidgets автоматически уничтожает дочерние объекты при уничтожении родительского диалога. Таким образом, вы не должны напрямую удалять контролы размещенные в окне, просто уничтожьте само диалоговое окно используя Destroy. Аналогично, при удалении фрейма автоматически удаляются все дочерние объекты внутри него. Однако, это правило не распространяется на дочерние окна верхнего уровня (top-level window). То есть, если вы создали окно верхнего уровня (например фрейм) дочерним другому окну верхнего уровня (другому фрейму), родительский фрейм не удалит дочерний. Но и из этого правило есть исключение – MDI интерфейс, в нем, дочерние фреймы не являются независимыми окнами и соответственно удаляются родителем.
Вы можете создать диалог в стеке, но это можно делать только для модальных диалогов: вызовите ShowModal для передачи управления в его цикл обработки сообщений, таким образом вся работа пользователя с диалогом, закончится до того как объект выйдет за пределы видимости и будет уничтожен.
Механизм закрытия и удаления оконных фреймов и диалогов, вполне может служить источником недоразумений и ошибок. Для удаления фрейма или модального диалога, приложение должно использовать Destroy. Эта функция реализует отложенное удаление, окно фактически удаляется только после того как будет обработана вся его очередь сообщений. Данное поведение намеренно сделано для того чтобы избежать посылки сообщений несуществующему окну. Однако, для модальных диалогов сначала должна быть вызвана функция EndModal, для выхода из цикла сообщений. Обработчик сообщения (к примеру для кнопки ОК), не должен уничтожать сам диалог если модальный диалог был создан в стеке, в таком случае он будет уничтожен дважды: первый раз из обработчика и еще раз когда объект покинет область видимости.
Когда пользователь закрывает модальный диалог, возникает событие wxEVT_CLOSE_WINDOW и соответствующий обработчик должен вызвать EndModal (не не должен разрушать диалог). По умолчанию поведение при нажатии кнопки закрытия в заголовке это эмуляция события wxID_CANCEL, обработчик которого обычно закрывает диалог. Затем диалог уничтожается при выходе из области видимости. Таким образом работают стандартные диалоги, например wxFileDialog или wxColourDialog, позволяя вам, тем самым, получить значения данных диалога после его закрытия и возврата управления.
Вы можете разработать модальный диалог, который уничтожит себя в обработчике события, но в этом случае вы не сможете создавать такой диалог в стеке или получать значения его данных после закрытия.
Ниже приведены два варианта использования wxMessageDialog.
[sourcecode language=”cpp”]
// 1) Создание диалога в стеке, без явного удаления
wxMessageDialog dialog(NULL, _(“Press OK”), _(“App”), wxOK|wxCANCEL);
if (dialog.ShowModal() == wxID_OK)
{
// 2) Создаем в куче, должны уничтожить используя Destroy()
wxMessageDialog* dialog = new wxMessageDialog(NULL,
_(“Thank you! “), _(“App”), wxOK);
dialog->ShowModal();
dialog->Destroy();
}
[/sourcecode]
Не модальные диалоги и фреймы обычно должны удалять себя сами при закрытии. Они не могут быть созданы в стеке, так как сразу же выйдут из области видимости и будут уничтожены.
Если вы храните указатель на оконный объект, обязательно обнуляйте его при разрушении окна. Код, решающий эту задачу, можно поместить в деструктор окна или обработчик закрытия. К примеру:
[sourcecode language=”cpp”]
void MyFindReplaceDialog::OnCloseWindow(wxCloseEvent& event)
{
wxGetApp().SetFindReplaceDialog(NULL);
Destroy();
}
[/sourcecode]
Создание и копирование объектов рисования (Drawing Objects)
Объекты рисования, такие как wxBrush, wxPen, wxColour, wxBitmap, и wxImage можно создавать как в стеке, так и в куче. Код, реализующий рисование, зачастую использует временные переменные созданные в стеке. Эти классы реализованы с использованием счетчика ссылок. А это значит, что вы может использовать сами объекты, а не указатели на них, со сравнительно малыми накладными расходами на копирование: то есть, при присваивании объекта кисти (wxBrush) другой переменной, в действительности происходит копирование только ссылки на данные.
Однако это поведение может иметь неожиданные побочные эффекты, когда изменение одной переменной, приводит к изменению другой. Чтобы избежать подобных связей между объектами, создавайте копии, используя конструктор и явно присваивайте свойства исходного объекта копии. Ниже приводится несколько примеров кода копирования как настоящего, так и с использования подсчета ссылок.
[sourcecode language=”cpp”]
// Копирование с подсчетом ссылок
wxBitmap newBitmap = oldBitmap;
// Реальное копирование
wxBitmap newBitmap = oldBitmap.GetSubBitmap(
wxRect(0, 0, oldBitmap.GetWidth(), oldBitmap.GetHeight()));
// Копирование с подсчетом ссылок
wxFont newFont = oldFont;
// Реальное копирование
wxFont newFont(oldFont.GetPointSize(), oldFont.GetFamily(),
oldFont.GetStyle(), oldFont.GetWeight(),
oldFont.GetUnderlined(), oldFont.GetFaceName());
[/sourcecode]
Инициализация объекта приложения
Объект приложения вполне может быть создан до того как wxWidgets инициализирует все свои внутренние объекты, например базу цветов или шрифт, используемый по умолчанию. Будьте осторожны и пользуйтесь такими объектами в конструкторе класса приложения. Вместо этого, инициализируйте эти члены класса в OnInit. К примеру:
[sourcecode language=”cpp”]
MyApp::MyApp()
{
// Не устанавливайте значение в этом месте!
// m_font = *wxNORMAL_FONT;
}
bool MyApp::OnInit()
{
m_font = *wxNORMAL_FONT;
…
return true;
}
[/sourcecode]
Очистка приложения (Cleanup)
Вы можете заменить функцию wxApp::OnExit и разместить в ней код для очистки приложения и удаления объектов. Эта функция вызывается уже после удаления всех окон приложения и сразу перед тем, как wxWidgets приступит к очистки своих внутренних данных. Однако, существуют ситуации, когда часть работы по очистке, необходимо выполнить в обработчике закрытия главного окна приложения. Например, в этом месте вы должны остановить процесс, работающий с членами данными класса окна, дабы избежать попытки чтения\записи уже несуществующих данных.