- Любовные романы
- Фантастика и фэнтези
- Ненаучная фантастика
- Ироническое фэнтези
- Научная Фантастика
- Фэнтези
- Ужасы и Мистика
- Боевая фантастика
- Альтернативная история
- Космическая фантастика
- Попаданцы
- Юмористическая фантастика
- Героическая фантастика
- Детективная фантастика
- Социально-психологическая
- Боевое фэнтези
- Русское фэнтези
- Киберпанк
- Романтическая фантастика
- Городская фантастика
- Технофэнтези
- Мистика
- Разная фантастика
- Иностранное фэнтези
- Историческое фэнтези
- LitRPG
- Эпическая фантастика
- Зарубежная фантастика
- Городское фентези
- Космоопера
- Разное фэнтези
- Книги магов
- Любовное фэнтези
- Постапокалипсис
- Бизнес
- Историческая фантастика
- Социально-философская фантастика
- Сказочная фантастика
- Стимпанк
- Романтическое фэнтези
- Ироническая фантастика
- Детективы и Триллеры
- Проза
- Юмор
- Феерия
- Новелла
- Русская классическая проза
- Современная проза
- Повести
- Контркультура
- Русская современная проза
- Историческая проза
- Проза
- Классическая проза
- Советская классическая проза
- О войне
- Зарубежная современная проза
- Рассказы
- Зарубежная классика
- Очерки
- Антисоветская литература
- Магический реализм
- Разное
- Сентиментальная проза
- Афоризмы
- Эссе
- Эпистолярная проза
- Семейный роман/Семейная сага
- Поэзия, Драматургия
- Приключения
- Детская литература
- Загадки
- Книга-игра
- Детская проза
- Детские приключения
- Сказка
- Прочая детская литература
- Детская фантастика
- Детские стихи
- Детская образовательная литература
- Детские остросюжетные
- Учебная литература
- Зарубежные детские книги
- Детский фольклор
- Буквари
- Книги для подростков
- Школьные учебники
- Внеклассное чтение
- Книги для дошкольников
- Детская познавательная и развивающая литература
- Детские детективы
- Домоводство, Дом и семья
- Юмор
- Документальные книги
- Бизнес
- Работа с клиентами
- Тайм-менеджмент
- Кадровый менеджмент
- Экономика
- Менеджмент и кадры
- Управление, подбор персонала
- О бизнесе популярно
- Интернет-бизнес
- Личные финансы
- Делопроизводство, офис
- Маркетинг, PR, реклама
- Поиск работы
- Бизнес
- Банковское дело
- Малый бизнес
- Ценные бумаги и инвестиции
- Краткое содержание
- Бухучет и аудит
- Ораторское искусство / риторика
- Корпоративная культура, бизнес
- Финансы
- Государственное и муниципальное управление
- Менеджмент
- Зарубежная деловая литература
- Продажи
- Переговоры
- Личная эффективность
- Торговля
- Научные и научно-популярные книги
- Биофизика
- География
- Экология
- Биохимия
- Рефераты
- Культурология
- Техническая литература
- История
- Психология
- Медицина
- Прочая научная литература
- Юриспруденция
- Биология
- Политика
- Литературоведение
- Религиоведение
- Научпоп
- Психология, личное
- Математика
- Психотерапия
- Социология
- Воспитание детей, педагогика
- Языкознание
- Беременность, ожидание детей
- Транспорт, военная техника
- Детская психология
- Науки: разное
- Педагогика
- Зарубежная психология
- Иностранные языки
- Филология
- Радиотехника
- Деловая литература
- Физика
- Альтернативная медицина
- Химия
- Государство и право
- Обществознание
- Образовательная литература
- Учебники
- Зоология
- Архитектура
- Науки о космосе
- Ботаника
- Астрология
- Ветеринария
- История Европы
- География
- Зарубежная публицистика
- О животных
- Шпаргалки
- Разная литература
- Зарубежная литература о культуре и искусстве
- Пословицы, поговорки
- Боевые искусства
- Прочее
- Периодические издания
- Фанфик
- Военное
- Цитаты из афоризмов
- Гиды, путеводители
- Литература 19 века
- Зарубежная образовательная литература
- Военная история
- Кино
- Современная литература
- Военная техника, оружие
- Культура и искусство
- Музыка, музыканты
- Газеты и журналы
- Современная зарубежная литература
- Визуальные искусства
- Отраслевые издания
- Шахматы
- Недвижимость
- Великолепные истории
- Музыка, танцы
- Авто и ПДД
- Изобразительное искусство, фотография
- Истории из жизни
- Готические новеллы
- Начинающие авторы
- Спецслужбы
- Подростковая литература
- Зарубежная прикладная литература
- Религия и духовность
- Старинная литература
- Справочная литература
- Компьютеры и Интернет
- Блог
О чём не пишут в книгах по Delphi - А. Григорьев
Шрифт:
Интервал:
Закладка:
Сравнить указатели на методы правильно можно с помощью типа TMethod из модуля SysUtils, объявленного следующим образом:
TMethod = record
Code, Data: Pointer;
end;
Так можно получать доступ к отдельным указателям, входящим в указатель на метод. Сравнение указателей на метод с помощью этого типа иллюстрирует листинг 3.61.
Листинг 3.61. Правильный способ сравнения указателей на методprocedure TForm1.Button2Click(Sender: TObject);
var
P1, P2: procedure of object;
begin
P1 := Button1.Update;
P2 := Button2.Update;
// Правильный способ сравнения указателей на методы
if (TMethod(P1).Data = TMethod(P2).Data) and
(TMethod(P1).Code = TMethod(P2).Code) then
Label1.Caption := 'Равно'
else Label1.Caption := 'He равно';
end;
Здесь мы явным образом заставляем компилятор сравнивать оба указателя, поэтому получаем правильный результат Не равно.
3.4.9. Возможность получения адреса свойства
Пусть у нас есть класс, описанный следующим образом (листинг 3.62).
Листинг 3.62. Класс со свойствами, читаемыми из переменной и из функцииTSomeClass = class private
FProp1: Integer;
function GetProp2: Integer;
public
property Prop1: Integer read FProp1;
property Prop2: Integer read GetProp2;
end;
В этом классе два свойства Prop1 и Prop2, значение одного из которых определяется полем FProp1, а другого — функцией GetProp2. Оба свойства предназначены только для чтения, но для того эффекта, о котором здесь пойдет речь, это не принципиально: свойства, значения которых можно менять, ведут себя в этом отношении точно так же.
Пусть X — это переменная типа TSomeClass. Легко убедиться, что компилятор допускает получение адреса свойства Prop1, т.е. конструкция вида @X.Prop1 считается допустимой. Результатом выполнении этого оператора станет указатель на поле FProp1. А вот конструкцию @X.Prop2 компилятор не допускает, выдаёт ошибку Variable required.
Ошибкой компилятора здесь является то, что он допускает получение адреса в первом случае, т.е. для свойства, значение которой берется из переменной. Это грубейшее нарушение принципа инкапсуляции, лежащего в основе объектно-ориентированного программирования, причем сразу по двум причинам. Во-первых, пользователь класса не должен видеть его внутреннюю реализацию, а здесь пользователь может определить, как читается свойство, по возможности применения оператора @ к нему. Во-вторых, пользователь класса должен взаимодействовать с ним строго через предоставленный интерфейс, а у нас получается, что, узнав адрес поля FProp1, пользователь сможет менять его значение в обход предусмотренных для этого в классе механизмов.
К счастью, ситуации, в которых эта недоработка компилятора могла бы принести пользу, крайне редки. Но если вы все-таки столкнулись с такой ситуацией, настоятельно рекомендуем не поддаваться соблазну и искать другие способы решения проблемы. Если класс, к полю которого вы хотите получить доступ таким образом, написан вами, то это веский повод пересмотреть внешний интерфейс класса, т.к. при его проектировании скорее всего, были допущены серьезные ошибки. Если это «чужой» класс, подумайте о том, что в следующей версии этого класса автор может изменить реализация свойства, и тогда ваш код откажется компилироваться.
3.4.10. Невозможность использования некоторых свойств оконного компонента в деструкторе
Проблема, о которой пойдет речь в этом разделе, гораздо шире, чем это явствует из заголовка. Однако наиболее ярко она проявится именно в этом случае. Поэтому мы начнем именно с этой ситуации, а потом рассмотрим проблему более широко.
Проблему демонстрирует пример ParentWnd на компакт-диске. В нем создан компонент TWrongCombo, наследник TComboBox. Листинг 5.67 содержит код компонента.
Листинг 3.63. Компонент TWrongCombotype
TWrongCombo = class(TComboBox)
public
destructor Destroy; override;
procedure AddItem(const Title: string);
end;
destructor TWrongCombo.Destroy;
var
I: Integer;
begin
for I := 0 to Items.Count - 1 do
if Assigned(Items.Objects[I]) then
Dispose(PDateTime(Items.Objects(I]));
inherited;
end;
procedure TWrongCombo.AddItem(const Title: string);
var
P: PDateTime;
begin
New(P);
P^ := Now;
Items.AddObject(Title, TObject(P));
end;
Класс TWrongCombo с каждым элементом, добавленным с помощью метода AddItem, связывает значение типа TDateTime, хранящее время добавления элемента. В разд. 3.4.6 мы уже познакомились с возможностью связывания данных с элементом списка с помощью свойства Items.Objects. Но так мы можем связать с элементом только 4-байтное значение, а тип TDateTime занимает 8 байтов. Поэтому значение TDateTime мы будем хранить в динамической памяти, а с элементом свяжем указатель на него.
Раз мы выделили динамическую память, ее нужно освободить при удалении компонента. Наиболее подходящим местом для этого кажется деструктор, и именно в нем помещен код освобождения выделенной памяти.
Теперь попробуем воспользоваться компонентом. На главной форме программы ParentWnd находится кнопка Wrong Combo, при нажатии на которую создается компонент типа TWrongCombo (листинг 3.64).
Листинг 3.64. Реакция на кнопку Wrong Comboprocedure TForm1.BtnWrongComboClick(Sender: TObject);
begin
if FWrongCombo = nil then
begin
FWrongCombo := TWrongCombo.Create(Self);
FWrongCombo.Left := 10;
FWrongCombo.Top := 10;
FWrongCombo.Parent := Self;
FWrongCombo.AddItem('One');
FWrongCombo.AddItem('Two');
FWrongCombo.AddItem('Three');
end;
end;
Теперь, если нажать эту кнопку и затем попытаться закрыть форму, в деструкторе TWrongCombo возникнет исключение EInvalidOperation с сообщением "Control has no parent window". Если откомпилировать программу с включенной опцией Use Debug DCUs, видно, что исключение возникает в методе TWinControl.CreateWnd. Одно только это способно обескуражить — действительно, зачем метод создания окна вызывается при его удалении?
Причина заключается в том, что к моменту вызова деструктора окно компонента уже удалено, свойство Handle имеет нулевое значение, и свойство Parent тоже имеет значение nil. Обращение к свойству Items.Count приводит к отправке окну сообщения CB_GETCOUNT. Отправка осуществляется с помощью функции SendMessage, одним из параметров которой является дескриптор окна, в качестве которого, естественно, передается свойство Handle. А это свойство, напомним, к этому моменту равно нулю. В разд. 1.1.7 обсуждалось, что обращение к этому свойству в тот момент, когда оно равно нулю, приводит к попытке создания окна (см. листинг 1.8). Именно поэтому вызывается метод CreateWnd. И он возбуждает исключение, потому что окно, которое создает компонент TWrongCombo, имеет стиль WS_CHILD, т.е. не может не иметь родителя. А родитель компоненту не назначен, поэтому и возникает исключение с таким странным, на первый взгляд, сообщением.
Отсюда следует важный вывод, что никакое обращение в деструкторе компонента к тем свойствам и методам, которые требуют наличия окна, невозможно. Окно уже удалено, а попытка создания нового окна приведет к исключению. Поэтому, например, в нашем коде требуется искать другое место, чтобы корректно освободить занятую память.
Поиск этого места оказывается не такой простой задачей, как хотелось бы, потому что разработчики VCL весьма странным образом реализовали удаление дочерних оконных компонентов в деструкторе класса TWinControl: сначала вызывается системная функция DestroyWindow, которая удаляет и само окно, и, разумеется, все дочерние окна, а потом только дочерние компоненты начинают уведомляться о том, что их удаляют, т.е. к этому моменту они уже не имеют возможности как-то задействовать свои окна. Соответственно, в нашем случае деструктор формы уничтожает окна всех дочерних компонентов до того, как будут вызваны деструкторы этих компонентов.
Положение спасает то, что об уведомлении окон заботится система Windows: всем окнам, которые удаляются в результате вызова функции DestroyWindow, отправляется сообщение WM_DESTROY, причем в момент получения этого сообщения ни окно, ни его родитель еще не уничтожены. Это позволяет компоненту как-то реагировать на свое удаление до того, как окно будет уничтожено.
Казалось бы, выход найден: нужно освобождать память в обработчике сообщения WM_DESTROY. Но и тут не все так просто. Дело в том, что окно может уничтожаться не только при удалении компонента, но и при изменении некоторых свойств (например, Parent). При этом окно удаляется, а вместо него создается новое, и при удалении старого окна компонент тоже получает сообщение WM_DESTROY. Что же касается компонента TComboBox, он обеспечивает, что при удалении и последующем создании окна все элементы, в том числе связанные с ними значения, восстанавливаются. Таким образом, если мы в наследнике TComboBox в обработчике сообщения WM_DESTROY всегда будем освобождать выделенную память, после повторного создания окна получим "битые" ссылки в свойстве Items.Objects, чего, естественно, хотелось бы избежать. Требуется научиться отличать полное удаление компонента от удаления окна с целью повторного создания.

