Опубликован: 17.10.2005 | Уровень: специалист | Доступ: свободно
Лекция 10:

Универсализация

Проверка типов

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

sc: STACK [CIRCLE]; sa: STACK [ACCOUNT]; c: CIRCLE; a: ACCOUNT.

Тогда в программах этого класса допустимы следующие инструкции:

sc.put (c) -- Втолкнуть круг в стек кругов
sa.put (a) -- Втолкнуть счет в стек счетов
c := sc.item -- Сущности круг присвоить вершину стека кругов.

Но каждая из следующих инструкций недопустима и будет отвергнута:

sc.put (a); -- Попытка: Втолкнуть счет в стек кругов.
sa.put (c); -- Попытка: Втолкнуть круг в стек счетов.
c:= sa.item -- Попытка: Дать кругу значение счета.

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

Правило типизации

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

Вначале рассмотрим обычные, не родовые классы. Пусть C такой класс. Рассмотрим объявление его компонента, не использующее, естественно, никаких формальных родовых параметров:

f(a:T):U is ...

Тогда вызов вида x.f(d), появляющийся в произвольном классе B, где x типа C, будет корректен по типу, тогда и только тогда, когда:

  • f доступен классу B, - экспортирован всем классам или множеству классов, включающих B ;
  • d принадлежит типу T. Если учитывать возможность наследования, то d может принадлежать потомкам T.
  • Результат вызова имеет тип U. В этом примере предполагается, что компонент f является функцией.

Теперь предположим, что C родовой класс с формальным родовым параметром G имеет компонент:

h (a: G): G is...

Вызов h имеет вид y.h(e), где y сущность, объявленная как

y: C [V]

Тип V - некоторый ранее определенный тип. Теперь правило типизации - двойник неродового правила - требует, чтобы e имело тип V или при наследовании было потомком V. Аналогичное требование к результату выполнения функции h.

Требования правила понятны: V - фактический параметр, заменяющий формальный родовой параметр G параметризованного класса C, поэтому он заменяет все вхождения G при вызове компонент класса. Все предыдущие примеры следовали этой модели: вызов s.put(z) требует параметра z типа POINT, если s типа STACK [POINT] ; INTEGER если s типа STACK [INTEGER] ; и s.item возвращает результат типа POINT в первом случае и типа INTEGER во втором.

Операции над сущностями родового типа

В родовом классе C [G, H, ...] рассмотрим сущность, чей тип - один из формальных родовых параметров, например x типа G. Когда класс используется клиентом для объявления сущностей, G, разумеется, может представлять любой тип. Поэтому любая операция, которую выполняют подпрограммы C над x, должна быть применима ко всем типам. Это ограничение позволяет выполнять только пять видов операций:

Использование сущностей формального родового типа

Корректно использовать сущность x, чей тип задан формальным родовым параметром G, можно следующим образом.

  1. Слева от оператора присваивания x := y, где выражение y также имеет тип G.
  2. Справа от оператора присваивания y := x, где сущность y также типа G.
  3. В логических выражениях вида x = y или x /= y, где y также типа G.
  4. Как фактический аргумент в вызове подпрограммы на месте формальных параметров типа G, или типа ANY.
  5. Как цель вызова компонента класса ANY.

В частности, инструкция создания вида create x неприменима, так как нам ничего неизвестно о процедурах создания, если таковые есть, для класса, определенного возможным фактическим родовым параметром, соответствующим G.

Случаи (4) и (5) ссылаются на класс ANY. Упомянутый уже несколько раз, этот класс содержит компоненты, наследуемые всеми классами. Поэтому можно быть уверенным, что независимо от фактического типа G при родовом порождении компоненты будут доступны. Компонентами класса ANY являются все основные операции копирования и сравнения объектов: clone, copy, equal, deep_clone, deep_equal и др. Поэтому для x и y формального родового типа G корректно использовать следующие инструкции:

x.copy (y)
x := clone (y)
if equal (x, y) then ...

Случай (4) разрешает вызов a.f(x) в родовом классе C [G], если f имеет формальный аргумент типа G. В частности, возможна ситуация, когда a может быть типа D [G], где D другой родовой класс. В классе D [G] объявлен компонент f, требующий аргумент типа G, обозначающий в этом случае формальный родовой параметр класса D, а не класса С. (Если предыдущая фраза не совсем понятна, перечитайте ее еще раз, и, надеюсь, она покажется столь же прозрачной2Для проверки прозрачности выполните упражнение У10.3. , как горный ручей.)

Типы и классы

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

С появлением универсализации второе утверждение перестало быть буквально истинным, хотя нюанс невелик. Родовой класс, объявленный как C [G], является не типом, а шаблоном типа, задающим бесконечное множество возможных типов. Любой тип из этого множества можно получить, предоставив фактический родовой параметр, который, в свою очередь, является типом.

Это приводит к более общему и гибкому понятию. Но за выигрыш в мощности приходится немного пожертвовать простотой: только при небольшом насилии над языком можно продолжать говорить о "компонентах класса T " или о "клиентах T ", если x объявлен, как имеющий тип T. Теперь T может быть параметрически порожденным типом C [U] из некоторого родового класса C и некоторого типа U. Конечно, основой типа остается родовой класс C, поэтому насилие над языком приемлемо.

Если требовать буквальной строгости, то терминология следующая. Любой тип T ассоциируется с базовым классом T, поэтому всегда можно говорить о компонентах и клиентах базового класса T. Если T неродовой класс, то он же является и базовым классом. Если T родовое порождение C [U, ...], то C является базовым классом T.

Базовые классы будут использоваться при введении еще одного вида типов, основанного также (как и все остальное в ОО-подходе) на классе, но косвенно: закрепленного типа (см. гл. 16.7).
Александр Шалухо
Александр Шалухо
Как сбросить прогресс по курсу? Хочу начать заново
Анатолий Садков
Анатолий Садков
Вопросик
Максим Шленкин
Максим Шленкин
Россия, г.Саранск
Александр Поветкин
Александр Поветкин
Россия, г. Надым