Ада-95. Компилятор GNAT

       

Примитивные и не примитивные операции над тэговыми типами Наследование операций


Концепция примитивной операции типа имеет важное значение в Аде.

В общем случае, примитивная операция над типом T - это или предопределенный знак операции (например, знаки операций для выполнения действий над целыми числами типа Integer), или операция (подпрограмма или знак операции) которая описывается в том же самом пакете в котором описывается тип T

вслед за описанием типа T, и принимает параметр типа T

или возвращает значение типа T (в случае функции).

При этом, необходимо обратить особое внимание на тот факт, что все примитивные операции являются наследуемыми.

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

Следует также заметить, что если тэговый тип должен иметь какие-либо примитивные операции, то он должен быть описан в спецификации пакета.

Описание тэгового типа в подпрограмме, с последующим описанием операций над этим тэговым типом не подразумевает того, что такие операции будут примитивными.

Также, если в пакете описывается другой тэговый тип, производный от первого описанного в этом пакете тэгового типа, то после описания второго тэгового типа становится невозможным описание примитивных операций для первого тэгового типа (обратное будет подразумевать, что второй теговый тип способен наследовать примитивные операции которые еще не описаны).

Для уточнения сказанного рассмотрим следующий пример:

package Simple_Objects is

type Object_1 is tagged

record

Field_1 : Integer; end record;

-- примитивные операции типа Object_1 procedure Method_1 (Self: in out Object_1);

type Object_2 is new Object_1 with

record

Field_2 : Integer; end record;

-- примитивные операции типа Object_2 procedure Method_2 (Self: in out Object_2); procedure Method_2 (Self: in out Object_1); -- НЕДОПУСТИМО!!! -- должна быть примитивной операцией для Object_1, -- но недопустима поскольку следует за описанием типа Object_2 -- который является производным от типа Object_1

end Simple_Objects;

<


В подобных случаях говорят, что описание типа Object_1

становится "замороженным" при обнаружении описания типа Object_2, производного от типа Object_1.

Подобное "замораживание" осуществляется также в случаях когда обнаруживается описание какого-либо объекта (переменной) типа Object_1.

Как только описание типа "заморожено", описание примитивных операций этого типа становится невозможным.

Следует также обратить внимание на то, как выполняется наследование примитивных операций, - подобных подпрограмме Method_1 для типа Object_1, в показанном выше примере, - для производного типа.

Подразумевается, что такие операции идентичным образом описываются неявно сразу за описанием производного типа.

Причем, тип парамета, где тип параметра соответствует типу предка, заменяется на производный тип.

Таким образом, для приведенного выше примера, в случае типа Object_2, выполняется неявное описание операции Method_1, наследуемой от типа-предка Object_1.

Такое неявное описание будет иметь следующий вид:

. . . procedure Method_1 (Self: in out Object_2); . . .

Бывают случаи, когда необходимо описать подпрограмму которая не будет наследоваться потомками тэгового типа, то есть, описать подпрограмму так, чтобы она не являлась примитивной операцией типа.

Типичными примерами таких подпрограмм могут служить функция, которая создает экземпляр объекта тэгового типа и возвращает его в качестве результата, или процедура инициализации полей объекта тэгового типа (назначение таких подпрограмм подобно конструкторам в других языках программирования).

Опасность наследования подобных подпрограмм заключается в том, что подпрограмма предка не имеет никаких сведений о дополнительных полях которые описаны в объекте-потомке как расширение типа-предка.

Следовательно, результат работы таких подпрограмм, в случае производного типа, будет не корректным (точнее - не полным).

Решением подобной проблемы, при описании пакета содержащего тэговый тип, может служить размещение описаний таких подпрограмм во внутреннем пакете.



В качестве примера, рассмотрим следующее описание:

package Simple_Objects is

type Object_1 is tagged

record

Field_1 : Integer; end record;

-- примитивные операции типа Object_1 procedure Method_1 (Self: in out Object_1); . . .

package Constructors is -- внутренний пакет содержащий не наследуемые -- операции типа Object_1

function Create (Field_1_Value: in Integer) return Object_1; . . .

end Constructors; . . .

end Simple_Objects;

Здесь, функция Create, которая возвращает значение типа Object_1, расположена во внутреннем пакете Constructors.

В результате такого описания, функция Create

(а также другие подпрограммы для типа Object_1, расположенные во внутреннем пакете Constructors) не будет наследоваться потомками типа Object_1

(типами, производными от типа Object_1).

В заключение обсуждения наследования операций над тэговыми типами, следует сделать одно важное замечание.

Поскольку производные типы явно не отображают операции которые они наследуют от своих типов-предков, то могут возникнуть трудности в понимании того, какие операции реально выполняются над объектами конкретного производного типа.

Действительно, для получения таких сведений необходимо изучить исходные тексты с описаниями всех типов-предков требуемого производного типа.

Осуществление этого может быть достаточно трудоемким процессом для сложной иерархии типов.

Для справедливости, следует заметить, что такая проблема характерна не только для Ады, но и для остальных объектно ориентированных языков программирования.


Содержание раздела