Rationale for Ada 2005: Access types
RUSTOPBACKNEXT
ENG |
3. Anonymous access types
@ As just mentioned, Ada 95 permits anonymous access types only as access parameters and access discriminants. And in the latter case only for limited types. Ada 2005 sweeps away these restrictions and permits anonymous access types quite freely. @ The main motivation for this change concerns type conversion. It often happens that we have a type T somewhere in a program and later discover that we need an access type referring to T in some other part of the program. So we introduce
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale for Ada 2005: Access types
@ENGRUSTOPBACKNEXT3. Анонимные ссылочные типы
@ Как уже было упомянуто, Ада 95 разрешает анонимные ссылочные типы только для ссылочных параметров и ссылочных дискриминантов. И в последнем случае только для ограниченных типов. Ада 2005 снимает эти ограничения и разрешает анонимные ссылочные типы весьма свободно.
@ Основным мотивом для этого изменения послужили проблемы с преобразованием типов. Часто случается, что в некоторм месте программы имеется тип T и затем в другой части программы возникает потребность обратиться по ссылке к переменной этого типа. Для этого мы вводим:
|
@ Затем может возникнуть необходимость в подобном ссылочном типе в другом месте программы. И мы объявляем другой ссылочный тип:
|
@ Если использование этих двух ссылочных типов наложится, тогда у нас появится необходимость выполнять явные преобразования из одного типа в доугой несмотря на то, что они действительно одинаковые. Конечно, можно заявить, что предварительное тщательное планирование спасло бы дело, но мы знаем, что программы часто развиваются непредсказуемым образом.
@ Более важный пример вынужденных действий явного преобразования типов касается ООП. Ссылочные типы используются весьма широко во многих аспектах OOП. У нас могла бы быть иерархия геометрических типов, начинающихся с корневого абстрактного типа Object следующим образом:
|
@ тогда мы могли бы объявить именнованные ссылочные типы так:
|
@ Понятно, что преобразование между ними должно быть разрешено во многих случаях. В некоторых случаях оно работает правильно, в других - требуется проверка во время выполнения. Таким образом, преобразование между Ref_Circle и Ref_Object всегда возможно, потому что каждое значение Ref_Circle - также значение Ref_Object, но обратное невозможно. Таким образом, мы могли бы иметь:
|
@ Однако, правила ada требуют, чтобы преобразования типов между именованными ссылочными типами были явными и давали имя типа. Это рассматривается многими программистами как обуза потому, что такие преобразования без именования типов разрешены в других языках OOП. Это не было бы настолько плохо, если бы явное преобразование требовалось только в тех случаях где проверка во время выполнения была бы действительно необходима.
@ Кроме того, эти преобразования весьма тривиальны, так как это всего лишь указатели и никакое реальное изменение значения не требуется; должна выполняться только проверка того, что значение - правильная ссылка целевого типа, и в большинстве случаев это уже ясно при трансляции. Такое требование именования типа только действует на нервы.
@ Фактически единственные преобразования между именованными теговыми типами (и именованными ссылочными типами), которые неявно разрешены в Аде, являются преобразования в надклассовый тип при инициализации или когда это - параметр (что практически - то же самое).
@ Было бы хорошо иметь возможность ослабить правила в Аде 2005, говоря, что именованное преобразование требуется только при проверке во время выполнения. Однако, такое изменение заставило бы много уже написанных программ становиться неоднозначными.
@ Так, вместо того, чтобы влезать в конверсионные правила, в Аде 2005 было разрешено использование анонимных ссылочных типов в большем количестве контекстов. У анонимных ссылочных типов есть интересное свойство, что они являются анонимными и стало быть не имеют названия, которое могло использоваться в преобразовании. Таким образом, мы можем написать:
|
@ Но мы не можем написать:
|
@ потому что общее правило состоит в том, что если требуется проверка тогда, преобразование должно быть явным. Таким образом, мы все еще должны будем ввести именованные ссылочные типы для некоторых преобразований.
@ Мы можем, конечно, использовать нулевые исключения с анонимными ссылочными типами следующим образом:
|
@ Объявление RO неудачно, потому что никакое начальное значение не задано, а устанавливаемое по умолчанию значение пустого указателя не разрешено и, таким образом, это вызывает исключение Constraint_Error; компилятор обнаружит это во время трансляции и выдаст предупреждение.
@ Отметим, что мы никогда никогда не пишем все с анонимными ссылочными типами.
@ Мы конечно, можем использовать константу с анонимными ссылочными типами. Обратите внимание на различие между следующим:
|
@ В первом случае ACT - переменная и может использоваться для обращения к различным объектам T1 и T2 типа T. Но она не может использоваться для измения значения этих объектов. Во втором случае CAT - константа и может только ссылаться на объект заданный при инициализации. Но мы можем изменить значение объекта, к которому обращается CAT. Таким образом, мы имеем:
|
@ На первый взгляд это может показаться запутанным, и было бы неплохо вообще запретить использование констант, таких как CAT (но разрешить ACT, которая вероятно более полезна, так как защищает менять значение, к которому обращаются). Но нехватку ортогональности считали весьма нежелательной. Кроме того, в Аде лево-рекурсивная грамматика, и мы знакомы с эквивалентными конструкциями такими как:
|
@ и
|
@ (хотя внимательный читатель отметит, что последний вариант незаконен, потому что по-дурацки использовано зарезервированное слово в как идентификатор).
@ Мы конечно, можем написать:
|
@ Тогда бъект CACT - константа и обеспечивает доступ только для чтения объекта T1 к которому обращается. Она не может быть изменена для обращения к другому объекту, такому как T2, и при этом значение T1 не может быть изменено через CACT.
@ Объект анонимного ссылочного типа, как и другие объекты, может быть объявлен как aliased следующим образом:
|
@ хотя такие конструкции, вероятно, будут редко использоваться.
@ Анонимные ссылочные типы также могут использоваться как компоненты массивов и записей. Во Введении мы видели, что вместо необходимости писать:
|
@ мы можем просто написать:
|
@ и это не только избавляет нас от необходимости объявлять именованный ссылочный тип Cell_Ptr, но также от необходимости в неполном описании типа Cell.
@ Разрешение этого потребовало небольшого изменения к правилу относительно использования именования типа в пределах его собственного объявления - так называемое правило текущего экземпляра класса.
@ Оригинальное текущее правило экземпляра класса состояло в том, что в пределах описания типа имя типа обращалось не к типу непосредственно, а к текущему объекту типа. Следующее описание задачного типа иллюстрирует и легальное и нелегальное использование имени типа задачи в пределах его собственного объявления. Это - по существу фрагмент программы из Раздела 18.10 [2], который находит простые числа многозадачной реализацией Решета Ерастофена. Каждая задача связана с простым числом, она удаляет множители этого числа и создаёт новую задачу, когда находит следующее простое число. Иными словами, задача должна делать клон самой себя.
|
@ Попытка сделать подчиненный клон задачи в функции Make_Clone незаконна, потому что в пределах задачного типа его имя обращается к текущему экземпляру класса а не к типу. Однако, оператор abort разрешен для завершения текущего экземпляра класса задачи. И решение состоит в том, чтобы просто разместить функцию Make_Clone вне тела задачи.
@ Однако, это правило предотвратило бы использование именованного типа Cell для объявления компоненты Next в пределах типа Cell, и это весьма неудобно, так как парадигма связанного списка очень распространена.
@ Чтобы разрешить эту проблему в Аде 2005 было изменено текущее правило экземпляра класса, чтобы позволить имени типа обозначать тип непосредственно в пределах анонимного объявления ссылочного типа (но не при объявлении именованного ссылочного типа). Таким образом, тип Cell разрешен.
@ Отметим, что в Аде 2005 задача TT все еще не может содержать объявление функции Make_Clone. Хотя мы больше не должны объявить именованный тип ATT, так как мы можем теперь объявить Ref_Clone так:
|
@ и мы можем объявить функцию так:
|
@ где у нас есть анонимный тип результата, однако оператор new TT внутри Make_Clone остается незаконным если Make_Clone объявлен в пределах тела задачи TT. Но такое использование не совсем обычно, тогда как объявление отличной внешней функции не составляет никаких проблем.
@ Чтобы быть честным, мы можем просто объявить подтип с другим именем вне задачи:
|
@ и затем мы можем написать new XTT (N) в функции и сохранить функцию скрытой в задаче.
@ Действительно, мы не нуждаемся в функции так или иначе, потому что мы можем только написать:
|
@ в теле задачи.
@ Введение более широкого использования анонимных ссылочных типов требует некоторого пересмотра правил сравнений типов и их преобразований. Это достигнуто введением типа universal_access по аналогии с типами universal_integer и universal_real. Два новых оператора равенства определены в пакете Standard следующим образом:
|
@ Литерал null, как теперь считают, имеет тип universal_access, и соответствующие преобразования определены также. Эти новые операции применимы только, когда по крайней мере один из параметров имеет анонимный ссылочныё тип (не считая null).
@ Интересные проблемы возникают, если мы определяем свою собственную операцию равенства. Например, предположим, что мы желаем сделать глубокое сравнение в двух списков, определенных типом Cell. Мы могли бы написать рекурсивную функцию со спецификацией:
|
@ Отметим, что ссылочные параметры более легки для использования чем параметры типа Cell непосредственно, потому что обеспечивают более естественное обслуживание случаев когда указатель null используется для обозначения пустого списка. Мы могли бы попытаться написать тело так:
|
@ Но это не работает, потому что вызовы "=" в первых двух строках рекурсивно вызывают объявляемую функцию, тогда как мы хотим вызвать предопределённую функцию "=" в этих случаях.
@ Трудность преодолевается если написать Standard."=" следующимобразом:
|
@ Общее правило относительно использования предопределенного равенства состоит в том, что оно не может использоваться, если есть определённая пользователем примитивная операция равенства для любого типа операнда, если мы не используем префикс Standard. Подобное правило относится к fixed point типам, что мы увидем в более поздней статье.
@ Другой пример использования типа Cell находится в предыдущей статье, когда мы обсуждали расширение типа на вложенных уровнях. Тот пример также иллюстрировал что ссылочные типы нужно называть при обстоятельствах, когда они обеспечивают полный тип для частного типа. Мы имели:
|
@ В этом случае мы должны назвать тип List, потому что это - частный тип. Однако, удобно использовать анонимный ссылочный тип, чтобы избежать неполного объявления Cell.
@ В процедуре Iterate локальная переменная This имеет также анонимный тип. Интересно заметить, что, если бы This был объявлен именованным типом List тогда, мы нуждались бы в явном преобразовании в:
|
@ Напомним, что мы всегда нуждаемся в явном преобразовании, преобразовывая в именованный ссылочный тип.
@ Таким образом, эффективное использование анонимных типов - целое искусство.
@ Во Введении рассматривались различные случаи использования анонимных ссылочных типов в массивах, записях и возвратах функций при обсуждении Ноева Ковчега и других ситуациях с животными. Теперь обратимся к более сложным вещам.
@ Важный вопрос в случае ссылочных типов - видимость. Правила видимости спроектированы так, чтобы предотвращать 'провисание' ссылок. Основное правило здесь состоит в том, что мы не можем создать значение ссылки, если у объекта на который она ссылается меньшая область видимости чем у ссылочного типа.
@ Однако есть обстоятельства, где это правило излишне серьезно, и это было одной из причин для введения ссылочных параметров. Возможно, некоторый обзор проблем был бы полезен. Рассмотрим:
|
@ Здесь у нас есть объект X с короткой жизнью, и мы не должны пользоваться ссылкой на X в объекте с более длинной жизнью, таком как Dodgy. Однако, мы хотим управлять X косвенно, используя процедуру P.
@ Если бы параметр имел именованный тип, такой как Ref_T как в случае процедуры Q, тогда этот вызов был бы незаконен в пределах Q, который мы могли тогда назначить на переменную, такую как Dodgy, которая сохранит "адрес" X после того, как X прекратил существование.
@ Однако, процедура P, которая использует ссылочный параметр, разрешает запрос. Причина в том, что ссылочные параметры несут динамическую информацию видимости относительно фактического параметра. Эта дополнительная информация дает возможность выполнять динамическую проверку, если мы попытаемся сделать что-то глупое в пределах процедуры, например выполнить присваивание на Dodgy. Преобразования в тип Ref_T в этом случае избавляет от сбоя.
@ Но отметим, что если мы имеем вызов P (Global'Access); где Global переменная объявленая на том же самом уровне где и Ref_T, тогда присваивание на Dodgy было бы разрешено.
@ Новые правила видимости для использования анонимных ссылочных типов очень просты. Уровень видимости это просто уровень объявления, и никакая динамическая информация при этом не используется. (Возможность сохранения динамической информации рассматривалась, но было признано неэффективным). Так объявление локальной переменной:
|
@ эквивалентно:
|
@ Подобная ситуация применяется для компонент записи или массива. Таким образом текст:
|
@ эквивалентен:
|
@ Если мы теперь объявим производный тип тогда никакого нового уровня видимости не создаётся:
|
@ В этом примере уровень видимости компоненты C производного типа является тем же самым как и родительского типа R и, таким образом, агрегат незаконен. Это несколько удивительное правило необходимо, чтобы предотвратить некоторые очень странные проблемы, которые мы не будем исследовать в этой статье.
@ Выжным последствием, которое необходимо знать, является то, что если мы устанавливаем значение в ссылочном параметре для локальной переменной анонимного ссылочного типа то динамическая видимость фактического параметра не будет проведена в локальной переменной. Рассмотрим снова пример процедуры P, содержащей присваивание для Dodgy:
|
@ и вариант, в котором мы ввели локальную переменную анонимного ссылочного типа:
|
@ Здесь мы скопировали значение параметра в локальную переменную перед попыткой присваивания Dodgy. (Фактически это не будет компилироваться, но позволяет нам анализировать это явление.) Преобразование в P с использованием ссылочного параметра Ptr является динамическим и потерпит неудачу если фактический параметр имеет уровень видимости больше чем у типа Ref_T. Т.е., если фактический параметр будет X возбудится исключение Program_Error, но всё будет нормально если параметр будет иметь такой же уровень как у типа Ref_T, такой как у переменной Global.
@ В случае P1, копирование из Ptr в Local_Ptr вызывает неявную конверсионную и статическую проверку, которая всегда выполняется. (Помните, что неявные преобразования запрещены, вовлекают ли они динамическую проверку.) Однако, преобразование в присваивании для Dodgy в P1 является также статическим и будет всегда терпеть неудачу независимо от того X или Global передаются как фактический параметр.
@ Таким образом, поведения P и P1 - одинаковое, если фактический параметр - X (они оба терпят неудачу, хотя один динамически и другой статически), но будет отличным, если фактический параметр будет иметь тот же самый уровень как тип Ref_T, типа переменной Global. Присваивание для Dodgy в P будет работать в случае Global, но присваивание для Dodgy в P1 никогда не будет работать.
@ Удивительно, казалось бы безвредное промежуточное назначение имеет существенный эффект из-за неявного преобразования и последующей потери информации видимости. На практике это вряд ли будет проблемой. В любом случае программисты должны знать, что ссылочные параметры имеют свои особенности и несут динамическую информацию.
@ В этом специфическом примере потеря информации видимости за счёт промежуточной локальной переменной обнаруживается во время компиляции. Но могут быть и более сложные случаи, котрые обнаруживается только во время выполнения. Предположим, что мы вводим третью процедуру Agent и изменяем P и P1 так:
|
@ Теперь мы видим, что P работает как прежде. Уровень видимости, который передаётся в P также передаётся в Agent, где выполняется присваивание Dodgy. Если параметр, который передают в P - локальный X тогда возбуждается Program_Error в Agent и далее размножается в P. Если параметр прошел как Global тогда, всё - хорошо.
@ Процедура P1 теперь компилируется, чего не было прежде. Однако, потому что видимость оригинального параметра потеряна назначением Local_Ptr, теперь это уровень видимости Local_Ptr, который передаётся в Agent, и это означает, что присваивание для Dodgy всегда приводит к возбуждению Program_Error независимо от того, вызывалось ли P1 с параметром X или Global.
@ Если мы по некоторым причинам хотим использовать другое имя, тогда мы можем избежать потери уровня видимости при использовании переименовывания следующим образом:
|
@ и это будет вести себя точно как оригинальная процедура P.
@ Как обычно переименовывание обеспечивает только другое представление того же самого объекта и, таким образом, сохраняет информацию видимости.
@ Переименовывание может также включить не пустой указатель:
|
@ Напомним, что not null никогда не может быть ложным, и таким образом, это законно только если тип Ptr действительно исключает пустой указатель (это будет, если Ptr будет управляющим ссылочным параметром процедуры P2).
@ Переименовывание оправдано, когда тип T имеет компоненты к которым в процедуре обращаются много раз. Например типом, к которому обращаются мог бы быть тип Cell, объявленный ранее. Тогда:
|
@ и это сохранит информацию о доступности.
@ Анонимные ссылочные типы могут также использоваться как результат функции. Во Введении мы имели функцию Mate_Of (A: access Animal'Class), возвращающую ссылку на Animal'Class; уровень видимости результата в этом случае - тот же самый что и при объявления функции непосредственно.
@ Мы можем также ссылаться на результат функции, если результат - ссылка на теговый тип. Рассмотрим:
|
@ Мы можем предположить, что T - теговый тип, представляющий некоторую категорию объектов, типа наших геометрических объектов и что Unit является функцией, возвращей объект типа круга или треугольника.
@ Мы могли бы также иметь функцию:
|
@ и тогда:
|
@ Здесь сначала будет вызов функции Unit затем её результат вместе с Thing будет параметром при вызове функции Is_Bigger.
@ Функция Unit также может использоваться как значение по умолчанию для параметра следующим образом:
|
@ Напомним, что значение по умолчанию, используемое в такой конструкции должно быть неопределенным тэгом.
@ Разрешение анонимных ссылочных типов как типов результата устраняет потребность определять понятие "возвращающий по ссылке" тип. Это было странным понятием в Аде 95 и, прежде всего, касалось ограниченных типов (включая задачи, и защищённые типы), который конечно не мог быть скопирован. Предоставление нам ссылки для записи непосредственно устраняет большой беспорядок. Ограниченные типы будут подробно обсуждаться в более поздней статье.
@ Ссылочные return-типы могут быть удобным способом получить постоянное представление таких объектов как таблицы.
@ Мы могли бы иметь массив в теле пакета (или в приватной части) и функцию в спецификации таким образом:
|
@ Мы можем тогда написать:
|
@ Это короче чем:
|
@ Но мы не можем написать:
|
@ хотя мы могли бы так сделать, если бы мы удалили constant из типа возвращения (когда мы должны использовать другое имя для функции).
@ Последнее новое использование анонимных ссылочных типов касается дискриминантов. Напомним, что дискриминант может иметь именованный ссылочный тип или анонимный ссылочный тип (так же как и все другие типы). Дискриминанты анонимного ссылочного типа известны как ссылочные дискриминанты. В Аде 95 ссылочные дискриминанты позволяются только с ограниченными типами. Дискриминанты именованного ссылочного типа - только дополнительные компоненты без специальных свойств. Но ссылочные дискриминанты ограниченных типов являются особенными. Так как тип ограничен, объект не может быть изменен целым рекордным назначением и, таким образом, дискриминант не может быть изменен, даже если он имеет значение по умолчанию. Тогда, тип Minor
|
@ Объекты Small и Large теперь связаны постоянно вместе.
@ В Аде 2005 ссылочные дискриминанты также разрешены для неограниченных типов. Однако, значения по умолчанию присваивается так, что дискриминант не может быть изменен снова и объекты связаны постоянно вместе. Интересный случай возникает, когда дискриминант назначается программой распределения таким образом:
|
@ В этом случае мы говорим, что распределенный объект - "объект общего происхождения" ("земляк") для Large. Объекты общего происхождения имеют ту же самую жизнь что и главный объект и прекращают своё существование вместе с ним. Есть различия в видимости и в других правилах относительно этих объектов, которые предотвращают трудности при возврате таких объектов из функций.
2010-10-24 00:26:54
. .