Современные технологии программирования |
Конспект лекций |
Лекция 9. Информация о типе времени исполнения (RTTI)
Изучаемые вопросы: * Информация о типе времени исполнения * Операции is и as * Класс Tobject * Конструктор Create и деструктор Destroy класса Tobject * Классовые методы класса Tobject * Прочие методы класса Tobject * TClass – тип указателей на Tobject * Контрольные вопросы * Источники дополнительных сведений *
RTTI класса.
Таблица виртуальных методов.
Таблица динамических методов.
Операция is
Операция as
Применение
Информация о типе времени исполнения
Когда компилятор встречает обращение к виртуальному методу, он подставляет вместо конкретного адреса код, который обращается к специальной таблице и извлекает оттуда нужный адрес. Эта таблица называется таблицей виртуальных методов (VMT – Virtual Method Table). Такая таблица есть для каждого объектного типа. В ней хранятся адреса всех виртуальных методов класса, независимо от того, унаследованы ли они от предка или перекрыты. Отсюда и достоинства и недостатки виртуальных методов: они вызываются сравнительно быстро, но медленнее статических, однако для хранения указателей на них требуется большое количество памяти.
Динамические методы вызываются медленнее, но позволяют более экономно расходовать память. Каждому динамическому методу системой присваивается уникальный индекс. В таблице динамических методов (DMT – Dynamic Method Table) класса хранятся индексы и адреса только тех динамических методов, которые описаны в данном классе. При вызове динамического метода происходит поиск в этой таблице; в случае неудачи просматриваются все классы-предки в порядке иерархии и, наконец, Tobject, где имеется стандартный обработчик вызова динамических методов. Экономия памяти налицо.
Для перекрытия и виртуальных и динамических методов служит директива override, с помощью которой (и только с ней) можно переопределять оба этих типа методов. Попытка применить override к статическому методу вызовет ошибку компиляции.
Рассмотрим, что представляет собой объект изнутри. Для этого рассмотрим иерархию, построенную из двух классов приведенных в примере ниже.
Пример 1. Иерархия классов.
Type
T1 = class
FF1: integer;
FF2: real;
Procedure StatMethod;
Procedure VirtMethod1 ;virtual;
Procedure VirtMethod2; virtual;
Procedure DynaMethod1; dynamic;
Procedure DynaMethod2; dynamic;
End;
T2 = class(T1)
Procedure StatMethod;
Procedure VirtMethod1; override;
Procedure DynaMethod1; override;
End;
Var
O: TObject;
O1: T1;
O2: T2;
begin
O:= TClass1.Create;
O.StatMethod;//Недопутимо
(O as TClass1).VirtMethod1;
(O as TClass1).DynaMethod1;
На рисунке показано, как будет выглядеть внутренняя структура рассмотренных в нём объектов.
Рисунок. Внутренняя структура объекта O1
Рисунок. Внутренняя структура объекта O2
Каждый экземпляр класса содержит копии всех его полей. Первое поле каждого экземпляра класса содержит указатель на его класс. Класс как структура состоит из двух частей. Начиная с адреса, на который ссылается указатель на класс, располагается таблица виртуальных методов (VMT). Она содержит адреса всех виртуальных методов, поэтому у O1 и O2 она имеет равную длину. Перед таблицей виртуальных методов расположена 32 байтовая структура, которая называется Информацией о типе времени выполнения (run type time information, RTTI). В ней содержатся данные, полностью характеризующие класс: его имя, размер экземпляра, указатель на класс предок, и т.д. на рисунке она показана одним блоком. Одно из полей структуры содержит адрес таблицы динамических методов класса. Обратите внимание, что у O2 она состоит из одного элемента.
В Object Pascal Информация о типе времени выполнения играет самостоятельную роль и может использоваться программистом явно или неявно.
Для работы с информацией о типе времени исполнения в Object Pascal введены две новые операции: is и as.
Операция is является бинарной, т.е. имеющей два операнда, и принимает следующий вид:
Объект is Класс
Она позволяет определить во время выполнения принадлежность Объекта указанному Классу или одному из его потомков. Результатом будет булевская величина, принимающая значение True, если Объект является объектом класса Класс или его потомком, и значение False – в противном случае. Операция is имеет низший приоритет наряду с операциями сравнения =, >,<,<=,>= и операцией in, и поэтому в выражениях заключается в скобки вместе со своими операндами.
Одним из часто используемых случаев использования операции is является безопасное приведение типов, для которого применяется конструкция такого вида:
if Объект is Класс then Класс(Объект).Метод;
Рассмотрим следующую иерархию классов:
Для демонстрации работы операции is приведём два следующих примера:
Пример 1. Использование операции is.
program PAssign;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
T = class
end;
T1 = class(T)
end;
T2 = class(T)
end;
T3= class(T)
end;
T11 = class(T1)
end;
T31 = class(T3)
end;
procedure P(O: T; ref: TClass);
begin
if O is ref then writeln(O.ClassName,' is ',ref.ClassName)
else writeln(O.ClassName,' is not ',ref.ClassName);
end;
var O:T;
begin
O:= T3.Create;
P(O,T3); //T3 is T3
O:= T31.Create;
P(O,T3); //T31 is T3
O:= T2.Create;
P(O,T3); //T2 is not T3
readln;
end.
Пример 2. Использование операции is.
unit UIs;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
Button3: TButton;
CheckBox1: TCheckBox;
procedure CommonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.CommonClick(Sender: TObject);
begin
if Sender is TButton then
Memo1.Lines.Add('Щелчок на одной из кнопок')
else
Memo1.Lines.Add('Щелчок не на кнопке')
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Form1.OnClick:= Form1.CommonClick;
Memo1.OnClick:= Form1.CommonClick;
Button1.OnClick:= Form1.CommonClick;
Button2.OnClick:= Form1.CommonClick;
Button3.OnClick:= Form1.CommonClick;
CheckBox1.OnClick:= Form1.CommonClick;
end;
initialization
end.
В этом примере показано выделение компонент-кнопок класса TButton из всего множества компонент формы, в случае использования общего для этих кнопок обработчика события OnClick. Если после запуска приложения на выполнение последовательно щелкнуть на форме Form1, поле Memo1, кнопках Button1, Button2, Button3 и контрольном поле CheckBox1, то получим следующий результат:
Операция as предназначена для преобразования типов, у неё так же, как и операции is два операнда, и её общий вид можно представить следующим образом:
Объект as Класс
Результатом операции as будет тот же самый Объект, но уже принадлежащий не к своему первоначальному классу, а к указанному в операции классу Класс.
Главное отличие операции as от прямого преобразования типов вида
Класс(Объект)
состоит в том, что при обработке операции as во время выполнения происходит проверка корректности преобразования типов. Если Объект является объектом типа Класс или его потомком, то преобразование выполняется. Если же нет, то возникает исключительная ситуация, для которой при необходимости можно написать свой обработчик. В случае прямого преобразования типов никаких проверок на совместимость типов не выполняется.
Эта операция имеет второй приоритет наряду с операциями умножения: *, /, div, mod, and, shl, shr.
Операция as используется для безопасного преобразования типов во время выполнения и практически эквивалентна вышеприведённой конструкции с использованием операции is. Но, в отличие от неё, позволяет писать более компактный и наглядный код, если используется вместе с оператором with.
with Объект as Класс do
или прямо в квалифицируемом идентификаторе
(Объект as Класс).Поле
(Объект as Класс).Метод
Для демонстрации работы операции as приведём следующий пример:
unit UIs;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
Button3: TButton;
CheckBox1: TCheckBox;
procedure CommonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.CommonClick(Sender: TObject);
begin
with Sender as TControl do
Memo1.Lines.Add('Щелчок на '+ Name)
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Form1.OnClick:= Form1.CommonClick;
Memo1.OnClick:= Form1.CommonClick;
Button1.OnClick:= Form1.CommonClick;
Button2.OnClick:= Form1.CommonClick;
Button3.OnClick:= Form1.CommonClick;
CheckBox1.OnClick:= Form1.CommonClick;
end;
initialization
end.
Класс Tobject – общий предок всех классов Delphi по умолчанию. Поэтому все классы могут использовать методы класса Tobject. Класс Tobject описан в Модулу System следующим образом:
TObject = class;
TClass = class of TObject;
TObject = class
constructor Create;
procedure Free;
class function InitInstance(Instance: Pointer): TObject;
procedure CleanupInstance;
function ClassType: TClass;
class function ClassName: ShortString;
class function ClassNameIs(const Name: string): Boolean;
class function ClassParent: TClass;
class function ClassInfo: Pointer;
class function InstanceSize: Longint;
class function InheritsFrom(AClass: TClass): Boolean;
class function MethodAddress(const Name: ShortString): Pointer;
class function MethodName(Address: Pointer): ShortString;
function FieldAddress(const Name: ShortString): Pointer;
function GetInterface(const IID: TGUID; out Obj): Boolean;
class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
class function GetInterfaceTable: PInterfaceTable;
function SafeCallException(ExceptObject: TObject;
ExceptAddr: Pointer): HResult; virtual;
procedure AfterConstruction; virtual;
procedure BeforeDestruction; virtual;
procedure Dispatch(var Message); virtual;
procedure DefaultHandler(var Message); virtual;
class function NewInstance: TObject; virtual;
procedure FreeInstance; virtual;
destructor Destroy; virtual;
end;
Рассмотрим назначение некоторых методов класса Tobject.
Поскольку в языке Object Pascal все объекты динамические то для их создания и уничтожения требуются конструктор и деструктор. Класс Tobject содержит конструктор Create и деструктор Destroy.
Конструктор Create и деструктор Destroy класса Tobject
Конструктор Create выполняет самые общие действия по созданию объектов:
Конструктор Create в классе Tobject объявлен не виртуальным и не содержит никаких параметров. Однако в некоторых стандартных классах-потомках Tobject он переопределяется.
Деструктор Destroy класса Tobject выполняет самые общие действия по уничтожению объектов, с помощью вызова метода FreeInstance класса Tobject, который в свою очередь:
Обратите внимание на важную особенность описания конструкторов и деструкторов. Поскольку деструктор Destroy в классе Tobject объявлен как виртуальный, то при его объявлении в пользовательских классах использование директивы override является необходимым условием для обеспечения корректных вызовов деструктора. То же самое касается конструктора Create, если он используется в описаниях классов, являющихся потомками классов TComponent, TComponentEditor.
Если в классе имеются поля ссылочных типов, в том числе классов, вам необходимо описать для класса собственный деструктор, так как наследуемый деструктор не знает, как освобождать поля этих типов.
Что касается деструктора Destroy и уничтожения обектов, то следует отметить, что класс Tobject содержит ещё один метод Free, предназначенный для той же цели. Метод Free сам вызывает деструктор destroy, но только в том случае, если есть что освобождать, то есть объект имеет значение отличное от nil.
Кроме конструктора Create и деструктора Destroy в классе Tobject определены также другие методы, среди которых есть как классовые методы, так и обычные.
Классовые методы класса Tobject
Метод ClassInfo
class function ClassInfo: Pointer;
Метод ClassInfo возвращает указатель на таблицу RTTI (Run-Time Type Information) указанного при вызове класса или объекта.
Метод ClassName
class function ClassName: ShortString;
Метод ClassName возвращает строку, содержащую имя класса, вызвавшего его объекта.
Метод ClassNameIs
class function ClassNameIs(const Name: string): Boolean;
Метод ClassNameIs возвращает булевское значение True, если параметр Name, являющийся длинной строкой, совпадает с именем класса, вызвавшего этот метод объекта.
Метод ClassParent
class function ClassParent: TClass;
Метод ClassParent возвращает указатель на класс непосредственного предка.
Метод InheritsFrom
class function InheritsFrom(AClass: TClass): Boolean;
Метод InheritsFrom возвращает булевское значение True, если класс, имя которого задано параметром AClass, является предком указанного при вызове класса или объекта.
Метод NewInstance
class function NewInstance: TObject; virtual;
Метод NewInstance выделяет память для объектов любого класса и возвращает указатель на выделенную область. Прямой вызов этого метода не рекомендуется, поскольку NewInstance автоматически вызывается любым конструктором.
Метод InstanceSize
class function InstanceSize: Longint;
Метод InstanceSize возвращает размер в байтах для объекта любого класса.
Метод InitInstance
class function InitInstance(Instance: Pointer): TObject;
Метод InitInstance инициализирует созданный объект нулевыми значениям.
Метод MethodAddress
class function MethodAddress(const Name: ShortString): Pointer;
Метод MethodAddress возвращает указатель на метод, заданный параметром Name, если этот метод является опубликованным (published), или nil в противном случае.
Метод MethodName
class function MethodName(Address: Pointer): ShortString;
Метод MethodName возвращает строку, содержащую имя опубликованного (published) метода, который расположен по адресу, заданному параметром Address. Если метод по адресу Address не является опубликованным, или Address вообще не указывает на метод, то возвращается пустая строка.
Метод ClassType
function ClassType: TClass;
Метод ClassType возвращает указатель на класс объекта, для которого был вызван этот метод, что позволяет динамически определять класс объекта в конкретный момент времени.
Метод CleanupInstance
procedure CleanupInstance;
Метод CleanupInstance выполняет корректное завершение работы с длинными строками и записями уничтожаемого объекта.
Метод FreeInstance
procedure FreeInstance; virtual;
Метод FreeInstance освобождает память, выделенную объекту ранее с помощью метода NewInstance. Во время работы вызывает методы InstanceSize и CleanupInstance.
Метод Dispatch
procedure Dispatch(var Message); virtual;
Метод Dispatch вызывает управляемые сообщениями методы.
Метод DefaultHandler
procedure DefaultHandler(var Message); virtual;
Метод DefaultHandler обеспечивает управление всеми сообщениями объекта, для которых он не имеет собственных обработчиков. Метод DefaultHandler класса Tobject не выполняет ничего, кроме возврата управления.
Метод FieldAddress
function FieldAddress(const Name: ShortString): Pointer;
Метод FieldAddress возвращает адрес заданного параметром Name поля объекта, если это поле является опубликованным (published) или nil, в противном случае.
TClass – тип указателей на Tobject
В Object pascal введён новый тип данных – указателей на классы. Аналогично предопределённым классам стандартные модули содержат и ряд предопределённых указателей на классы. Особая роль среди этих типов, так же как и классу Tobject принадлежит типу TClass, который является типом указателей на класс Tobject и может быть использован для доступа к таблице RTTI класса Tobject.
Источники дополнительных сведений