Rationale for Ada 2005: Introduction

RUSTOP
BACKNEXT

ENG

3.2 Access types

@ It has been said that playing with pointers is like playing with fire - properly used all is well but carelessness can lead to disaster. In order to avoid disasters, Ada 95 takes a stern view regarding the naming of access types and their conversion. However, experience has shown that the Ada 95 view is perhaps unnecessarily stern and leads to tedious programming.

@ We will first consider the question of giving names to access types. In Ada 95 all access types are named except for access parameters and access discriminants. Thus we might have

  1        type Animal is tagged record Legs: Integer; ... end record;
  2        type Acc_Animal is access Animal; -- named
  3        procedure P (Beast : access Animal; ... ); -- anonymous

@ Moreover, there is a complete lack of symmetry between named access types and access parameters. In the case of named access types, they all have a null value (and this is the default on declaration if no initial value be given). But in the case of access parameters, a null value is not permitted as an actual parameter. Furthermore, named access types can be restricted to be access to constant types such as

  1        type Rigid_Animal is access constant Animal;

@ which means that we cannot change the value of the Animal referred to. But in the case of access parameters, we cannot say

  1        procedure P (Beast : access constant Animal); -- not 95

@ In Ada 2005 almost all these various restrictions are swept away in the interests of flexibility and uniformity.

@ First of all we can explicitly specify whether an access type (strictly subtype) has a null value. We can write

  1        type Acc_Animal is not null access all Animal'Class;

@ This means that we are guaranteed that an object of type Acc_Animal cannot refer to a null animal. Therefore, on declaration such an object should be initialized as in the following sequence

  1        type Pig is new Animal with ... ;
  2        Empress_Of_Blandings: aliased Pig := ... ;
  3        My_Animal : Acc_Animal := Empress_Of_Blandings'Access; -- must initialize

@ (The Empress of Blandings is a famous pig in the novels concerning Lord Emsworth by the late P G Wodehouse.) If we forget to initialize My_Animal then Constraint_Error is raised; technically the underlying type still has a null value but Acc_Animal does not. We can also write not null access constant of course.

@ The advantage of using a null exclusion is that when we come to do a dereference

  1        Number_of_Legs : Integer := My_Animal.Legs;

@ then no check is required to ensure that we do not dereference a null pointer. This makes the code faster.

@ The same freedom to add constant and not null also applies to access parameters. Thus we can write all of the following in Ada 2005

  1        procedure P (Beast : access Animal);
  2        procedure P (Beast : access constant Animal);
  3        procedure P (Beast : not null access Animal);
  4        procedure P (Beast : not null access constant Animal);

@ Note that all is not permitted in this context since access parameters always are general (that is, they can refer to declared objects as well as to allocated ones).

@ Note what is in practice a minor incompatibility, the first of the above now permits a null value as actual parameter in Ada 2005 whereas it was forbidden in Ada 95. This is actually a variation at runtime which is normally considered abhorrent. But in this case it just means that any check that will still raise Constraint_Error will be in a different place - and in any event the program was presumably incorrect.

@ Another change in Ada 2005 is that we can use anonymous access types other than just as parameters (and discriminants). We can in fact also use anonymous access types in

@ Thus we can extend our farmyard example

  1        type Horse is new Animal with ... ;
  2        type Acc_Horse is access all Horse;
  3        type Acc_Pig is access all Pig;
  4        Napoleon, Snowball : Acc_Pig := ... ;
  5        Boxer, Clover      : Acc_Horse := ... ;

@ and now we can declare an array of animals

  1        Animal_Farm : constant array (Positive range <>)
  2                of access Animal'Class := (Napoleon, Snowball, Boxer, Clover);

@ (With acknowledgments to George Orwell.) Note that the components of the array are of an anonymous access type. We can also have record components of an anonymous type

  1        type Ark is
  2                record
  3                        Stallion, Mare : access Horse;
  4                        Boar, Sow      : access Pig;
  5                        Cockerel, Hen  : access Chicken;
  6                        Ram, Ewe       : access Sheep;
  7                        ...
  8                end record;
  9        Noahs_Ark : Ark := (Boxer, Clover, ... );

@ This is not a very good example since I am sure that Noah took care to take actual animals into the Ark and not merely their addresses.

@ A more useful example is given by the classic linked list. In Ada 95 (and Ada 83) we have type Cell;

  1        type Cell_Ptr is access Cell;
  2        type Cell is
  3                record
  4                        Next  : Cell_Ptr;
  5                        Value : Integer;
  6                end record;

@ In Ada 2005, we do not have to declare the type Cell_Ptr in order to declare the type Cell and so we do not need to use the incomplete declaration to break the circularity. We can simply write

  1        type Cell is
  2                record
  3                        Next  : access Cell;
  4                        Value : Integer;
  5                end record;

@ Here we have an example of the use of the type name Cell within its own declaration. In some cases this is interpreted as referring to the current instance of the type (for example, in a task body) but the rule has been changed to permit its usage as here.

@ We can also use an anonymous access type for a single variable such as

  1        List : access Cell := ... ;

@ An example of the use of an anonymous access type for a function result might be in another animal function such as

  1        function Mate_Of (A : access Animal'Class) return access Animal'Class;

@ We could then perhaps write

  1        if Mate_Of (Noahs_Ark.Ram) /= Noahs_Ark.Ewe then
  2                ... -- better get Noah to sort things out
  3        end if;

@ Anonymous access types can also be used in a renaming declaration. This and other detailed points on matters such as accessibility will be discussed in a later paper.

@ The final important change in access types concerns access to subprogram types. Access to subprogram types were introduced into Ada 95 largely for the implementation of callback. But important applications of such types in other languages (going back to Pascal and even Algol 60) are for mathematical applications such as integration where a function to be manipulated is passed as a parameter. The Ada 83 and Ada 95 approach has always been to say "use generics". But this can be clumsy and so a direct alternative is now provided.

@ Recall that in Ada 95 we can write

  1        type Integrand is access function (X : Float) return Float;
  2        function Integrate (Fn : Integrand; Lo, Hi : Float) return Float;

@ The idea is that the function Integrate finds the value of the integral of the function passed as parameter Fn between the limits Lo and Hi. This works fine in Ada 95 for simple cases such as where the function is declared at library level. Thus to evaluate integral from 0.0 to 1.0 for X by dX

@ we can write

  1        Result := Integrate (Sqrt'Access, 0.0, 1.0);

@ where the function Sqrt is from the library package Ada.Numerics.Elementary_Functions.

@ However, if the function to be integrated is more elaborate then we run into difficulties in Ada 95 if we attempt to use access to subprogram types. Consider the following example which aims to compute the integral of the expression xy over the square region 0 ? x, y ? 1.

  1        with Integrate;
  2
  3        procedure Main is
  4
  5                function G (X : Float) return Float is
  6
  7                        function F (Y : Float) return Float is
  8                        begin
  9                                return X * Y;
 10                        end F;
 11                begin
 12                        return Integrate (F'Access, 0.0, 1.0); -- illegal in 95
 13                end G;
 14
 15                Result : Float;
 16        begin
 17                Result := Integrate (G'Access, 0.0, 1.0); -- illegal in 95
 18                ...
 19        end Main;

@ But this is illegal in Ada 95 because of the accessibility rules necessary with named access types in order to prevent dangling references. Thus we need to prevent the possibility of storing a pointer to a local subprogram in a global structure. This means that both F'Access and G'Access are illegal in the above.

@ Note that although we could make the outer function G global so that G'Access would be allowed nevertheless the function F has to be nested inside G in order to gain access to the parameter X of G. It is typical of functions being integrated that they have to have information passed globally - the number of parameters of course is fixed by the profile used by the function Integrate.

@ The solution in Ada 2005 is to introduce anonymous access to subprogram types by analogy with anonymous access to object types. Thus the function Integrate becomes

  1        function Integrate
  2        (       Fn     : access function (X : Float) return Float;
  3                Lo, Hi : Float) return Float;

@ Note that the parameter Fn has an anonymous type defined by the profile so that we get a nesting of profiles. This may seem a bit convoluted but is much the same as in Pascal.

@ The nested example above is now valid and no accessibility problems arise. (The reader will recall that accessibility problems with anonymous access to object types are prevented by a runtime check; in the case of anonymous access to subprogram types the corresponding problems are prevented by decreeing that the accessibility level is infinite - actually the RM says larger than that of any master which comes to the same thing.)

@ Anonymous access to subprogram types are also useful in many other applications such as iterators as will be illustrated later.

@ Note that we can also prefix all access to subprogram types, both named and anonymous, by constant and not null in the same way as for access to object types.

Rationale for Ada 2005: Introduction

ENGRUSTOP
BACKNEXT

3.2 Ссылочные типы

@ Несомненно, игра с указателями походит на игру с огнем - при их корректном использовании всё хорошо, но небрежность с ними может привести к непредсказуемым последствиям. Чтобы избежать подобных проблем Ада 95 имеет строгие правила относительно обозначения и преобразования ссылочных типов. Однако опыт показал, что эти правила в Аде 95, возможно излишне строги и приводят к утомительному программированию.

@ Рассмотрим сначала вопрос предоставления имен ссылочным типам. В Аде 95 все ссылочные типы именуются (за исключением ссылочных параметров и ссылочных дискриминантов). Таким образом, мы могли бы написать:

  1        type Animal is tagged record Legs: Integer; ... end record;
  2        type Acc_Animal is access Animal; -- named
  3        procedure P (Beast : access Animal; ... ); -- anonymous

@ Кроме того, есть определённая асимметрия между именованными ссылочными типами и ссылочными параметрами. В случае именованных ссылочных типов у них всех есть нулевое значение (и это - значение по умолчанию при объявлении, если явно не задано другое значение). Но в случае ссылочных параметров, нулевое значение как фактический параметр не разрешается. Кроме того, именованные ссылочные типы могут ссылаться на константные типы:

  1        type Rigid_Animal is access constant Animal;

@ что означает, что мы не можем изменить значение Animal через ссылку типа Rigid_Animal. Но в случае ссылочных параметров, мы не можем написать:

  1        procedure P (Beast : access constant Animal); -- not 95

@ В Аде 2005 почти все эти ограничения отменены в интересах гибкости и однородности.

@ Прежде всего, мы можем явно определить есть ли у ссылочного типа (строго подтипа) нулевое значение. Мы можем написать:

  1        type Acc_Animal is not null access all Animal'Class;

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

  1        type Pig is new Animal with ... ;
  2        Empress_Of_Blandings : aliased Pig := ... ;
  3        My_Animal            : Acc_Animal := Empress_Of_Blandings'Access; -- must initialize

@ (Empress of Blandings - известная свинья в романах по отношению к лорду Emsworth в последнем G P Wodehouse). Если мы забудем инициализировать My_Animal, то возбуждается исключение Constraint_Error; технически основному типу всё ещё разрешается иметь нулевое значение, но Acc_Animal уже нет. Конечно, мы можем также использовать не пустой указатель для обращения к константе.

@ Преимущество запрета нулевого указателя состоит в этом, что когда мы делаем разыменовывание:

  1        Number_of_Legs : Integer := My_Animal.Legs;

@ при этом не выполняется никакой проверки того что мы не разыменовываем нулевой указатель. Это делает код быстрее.

@ Так же мы можем добавить атрибуты constant и/или not null access к ссылочным параметрам. Таким образом, на Аде 2005 мы можем написать:

  1        procedure P (Beast : access Animal);
  2        procedure P (Beast : access constant Animal);
  3        procedure P (Beast : not null access Animal);
  4        procedure P (Beast : not null access constant Animal);

@ Отметим, что не всё разрешено в этом контексте, так как ссылочные параметры всегда являются общими (то есть, они могут ссылаться как к объявленным (declared) объектам так и к распределенным (allocated)).

@ Отметим небольшое отличие, на Аде 2005 теперь разрешается нулевое значение в качестве фактического параметра, тогда как это запрещено в Аде 95. Это - фактически изменение во время выполнения, которое обычно считается отвратительным. Но в этом случае это только означает, что любая проверка, которая все еще возбуждает исключение Constraint_Error будет в другом месте - и, в любом случае, это означает что программа была, скорее всего, неправильной.

@ Другое изменение в Аде 2005 состоит в том, что мы также можем использовать анонимные ссылочные типы в качестве параметров (и дискриминантов). Мы также можем использовать анонимные ссылочные типы:

@ Таким образом, мы можем расширить свой пример фермерского двора:

  1        type Horse is new Animal with ... ;
  2        type Acc_Horse is access all Horse;
  3        type Acc_Pig is access all Pig;
  4        Napoleon, Snowball : Acc_Pig   := ... ;
  5        Boxer, Clover      : Acc_Horse := ... ;

@ и можем объявить массив животных:

  1        Animal_Farm : constant array (Positive range <>)
  2                of access Animal'Class := (Napoleon, Snowball, Boxer, Clover);

@ (За подтверждениями к Джорджу Орвеллу). Обратите внимание, что компоненты массива имеют анонимный ссылочный тип. У нас могут также быть компоненты записи анонимного типа

  1        type Ark is
  2                record
  3                        Stallion, Mare : access Horse;
  4                        Boar, Sow      : access Pig;
  5                        Cockerel, Hen  : access Chicken;
  6                        Ram, Ewe       : access Sheep;
  7                        ...
  8                end record;
  9        Noahs_Ark : Ark := (Boxer, Clover, ... );

@ Это не очень хороший пример, так как я уверен, что Ной позаботился бы взять в Ковчег реальных животных а не их адреса.

@ Более полезный пример представлен классическим связанным списком. На Аде 95 (и Аде 83) мы делали бы это так:

  1        type Cell_Ptr is access Cell;
  2        type Cell is
  3                record
  4                        Next  : Cell_Ptr;
  5                        Value : Integer;
  6                end record;

@ На Аде 2005 мы не обязаны объявлять тип Cell_Ptr чтобы объявить тип Ceil и, таким образом, мы не должны использовать неполное объявление, чтобы избежать зацикливания:

  1        type Cell is
  2                record
  3                        Next  : access Cell;
  4                        Value : Integer;
  5                end record;

@ Здесь мы имеем пример использования типа по имени Ceil в пределах его собственного объявления. В некоторых случаях это интерпретируется как обращение к текущему экземпляру типа (например, в теле задачи), но это правило было изменено чтобы разрешить его использование здесь.

@ Мы можем также использовать анонимный ссылочный тип для единственной переменной:

  1        List : access Cell := ... ;

@ Мы тогда можем написать:

  1        if Mate_Of (Noahs_Ark.Ram) /= Noahs_Ark.Ewe then
  2                ... -- better get Noah to sort things out
  3        end if;

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

@ Последнее важное изменение в проблемах ссылочных типов относится к ссылочным типам на подпрограммы. Ссылки к типам подпрограммы были введены в Аду 95 в значительной степени для реализации обратного вызова. Но важные приложения этих типов в других языках (таких как Паскаль, и даже Алгол 60) применяются для математических приложений, таких как интеграция, где функцию, которая будет вычисляться, передают как параметр. На Аде 83 и Аде 95 в этом случае мы должны были всегда использовать настраиваемые средства. Но это было весьма неуклюже и теперь этому есть прямая альтернатива.

@ На Аде 95 мы написали бы:

  1        type Integrand is access function (X : Float) return Float;
  2        function Integrate (Fn : Integrand; Lo, Hi : Float) return Float;

@ Идея состоит в том, что функция Integrate вычисляет значение определённого интеграла функции передаваемой параметром Fn с границами Lo и Hi. Это прекрасно работает на Аде 95 для простого случая когда функция объявлена на библиотечном уровне. Таким образом, чтобы вычислить определённый интеграл квадратного корня по переменной X в диапазоне от 0.0 до 1.0 мы можем написать:

  1        Result := Integrate (Sqrt'Access, 0.0, 1.0);

@ где функция Sqrt из библиотеки Ada.Numerics.Elementary_Functions.

@ Однако, если функция для интегрирования более сложна, тогда на Аде 95 мы сталкиваемся с трудностями если мы пытаемся использовать ссылочный тип на подпрограмму. Рассмотрим следующий пример, в котором собираемся вычислять интеграл произведения X * Y в квадратной области 0.0 < X, Y > 1.0

  1        with Integrate;
  2        procedure Main is
  3
  4                function G (X : Float) return Float is
  5
  6                        function F (Y : Float) return Float is
  7                        begin
  8                                return X * Y;
  9                        end F;
 10                begin
 11                        return Integrate (F'Access, 0.0, 1.0); -- illegal in 95
 12                end G;
 13
 14                Result : Float;
 15        begin
 16                Result := Integrate (G'Access, 0.0, 1.0); -- illegal in 95
 17                ...
 18        end Main;

@ Но это недопустимо на Аде 95 из-за правила для именованных ссылочных типов запрещающего провисание ссылок. Таким образом, мы должны предотвратить возможность сохранения указателя на локальную подпрограмму в глобальной структуре. Это означает, что использование F'Access и G'Access незаконно в вышеупомянутом.

@ Заметим, что, хотя мы могли сделать внешнюю глобальную переменную функции G так, чтобы G'Access был разрешен, однако функция F должена быть вложена в функцию G чтобы получить доступ к параметру X из G. Это типично для функций интегрирования, им нужно передавать информацию глобально - число параметров конечно устанавливается конфигурацией используемой функции Integrate.

@ На Аде 2005 решение состоит в том, чтобы ввести анонимный доступ к типу подпрограммы по аналогии с анонимными ссылками на типы объекта. Таким образом, функция Integrate становится:

  1        function Integrate
  2        (       Fn     : access function (X : Float) return Float;
  3                Lo, Hi : Float) return Float;

@ Заметим, что определяя функцию Fn внутри спецификации функции Integrate мы фактически получаем вложение спецификаций. Это может казаться немного замысловатым, но такая же картина получается и на Паскале.

@ Вложенный пример выше теперь правилен, и никаких проблем доступа здесь не возникает. (Читатель возразит, что проблемы доступа с анонимным доступом к типам объекта предотвращаются динамической проверкой; в случае анонимного доступа к типам подпрограммы соответствующие проблемы предотвращаются подразумевая, что уровень доступности фактически бесконечен, RM говорит больший чем тот из любого задачника, который прибывает в ту же самую вещь).

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

@ Заметим, что мы можем также указать префикс чтобы обозначить ссылку на тип подпрограммы как анонимный и как константу с не пустым указателем.

ENG RUS

TOP BACK NEXT

2010-10-24 00:26:52

мужское нижнее белье . .