Rationale for Ada 2005: Object oriented model

RUSTOP
BACKNEXT

ENG

7. Overriding and overloading

@ One of the key goals in the design of Ada was to encourage the writing of correct programs. It was intended that the structure, strong typing, and so on should ensure that many errors which are not detected by most languages until run time should be caught at compile time in Ada. Unfortunately the introduction of type extension and overriding in Ada 95 produced a situation where careless errors in subprogram profiles lead to errors which are awkward to detect.

@ The Introduction described two typical examples. The first concerns the procedure Finalize.

@ Consider

  1        with Ada.Finalization; use Ada.Finalization;
  2        package Root is
  3                type T is new Controlled with ... ;
  4                procedure Op (Obj : in out T; Data : in Integer);
  5                procedure Finalise (Obj : in out T);
  6        end Root;

@ We have inadvertently written Finalise rather than Finalize. This means that Finalize does not get overridden as expected and so the expected behaviour does not occur on finalization of objects of type T.

@ In Ada 2005 we can prefix the declaration with overriding

  1        overriding procedure Finalize (Obj : in out T);

@ And now if we inadvertently write Finalise then this will be detected during compilation.

@ Similar errors can occur in a profile. If we write

  1        package Root.Leaf is
  2                type NT is new T with null record;
  3                overriding -- overriding indicator
  4                        procedure Op (Obj : in out NT; Data : in String);
  5                end Root.Leaf;

@ then the compiler will detect that the new procedure Op has a parameter of type String rather than Integer.

@ However if we do want a new operation then we can write

  1        not overriding procedure Op (Obj : in out NT; Data : in String);

@ The overriding indicators can also be used with abstract subprograms, null procedures, renamings, instantiations, stubs, bodies and entries (we will deal with entries in the paper on tasking). So we can have

  1        overriding procedure Pap (X : TT) is abstract;
  2        overriding procedure Pep (X : TT) is null;
  3        overriding procedure Pip (Y : TT) renames Pop;
  4        not overriding procedure Poop is new Peep ( ... );
  5        overriding procedure Pup (Z : TT) is separate;
  6        overriding procedure Pup (X : TT) is begin ... end Pup;

@ We do not need to apply an overriding indicator to both a procedure specification and body but if we do then they naturally must not conflict. It is expected that overriding indicators will typically only be given on specifications but they would be appropriate in the case of a body standing alone as in the example of Action in the previous section. So we might have

  1        procedure Green_To_Red (L : in List) is
  2                type GTR_It is new Iterator with null record;
  3                overriding procedure Action (It : in out GTR_It; C : in out Colour) is
  4                begin
  5                        if C = Green then C := Red; end if;
  6                end Action;
  7                ...

@ The overriding indicators are optional for two reasons. One is simply for compatibility with Ada 95. The other concerns awkward problems with private types and generics.

@ Consider

  1        package P is
  2                type NT is new T with private;
  3                procedure Op (X : T);
  4        private

@ Now suppose the type T does not have an operation Op. Then clearly it would be wrong to write

  1        package P is
  2                type NT is new T with private;     -- T has no Op
  3                overriding -- illegal
  4                        procedure Op (X : T);
  5        private

@ because that would violate the information known in the partial view.

@ But suppose that in fact it turns out that in the private part the type NT is actually derived from TT (itself derived from T) and that TT does have an operation Op.

  1        private
  2                type NT is new TT with ... -- TT has Op
  3        end P;

@ In such a case it turns out in the end that Op is in fact overriding after all. We can then put an overriding indicator on the body of Op since at that point we do know that it is overriding.

@ Equally of course we should not specify not overriding for Op in the visible part because that might not be true either (since it might be that TT does have Op). However if we did put not overriding on the partial view then that would not in itself be an error but would simply constrain the full view not to be overriding and thus ensure that TT does not have Op.

@ Of course if T itself has Op then we could and indeed should put an overriding indicator in the visible part since we know that to be the truth at that point.

@ The general rule is not to lie. But the rules are slightly different for overriding and not overriding. For overriding it must not lie at the point concerned. For not overriding it must not lie anywhere.

@ This asymmetry is a bit like presuming the prisoner is innocent until proved guilty. We sometimes start with a view in which an operation appears not to be overriding and then later on we find that it is overriding after all. But the reverse never happens – we never start with a view in which it is overriding and then later discover that it was not. So the asymmetry is real and justified.

@ There are other similar but more complex problems with private types concerning implicit declarations where the implicit declaration turns up much later and is overriding but has no physical presence on which to hang the indicator. It was concluded that by far the best approach to these problems was just to say that the overriding indicator is always optional. We cannot expect to find all the bugs in a program through syntax and static semantics; the key goal here is to provide a simple way of finding most of them.

@ Similar problems arise with generics. As is usual with generics the rules are checked in the generic itself and then rechecked upon instantiation (in this case for uses within both the visible part and private part of the specification). Consider

  1        generic
  2                type GT is tagged private;
  3        package GP is
  4                type NT is new GT with private;
  5                overriding -- illegal, GT has no Op
  6                        procedure Op (X : NT);
  7        private

@ This has to be illegal because GT has no operation Op. Of course the actual type at instantiation might have Op but the check has to pass both in the generic and in the instantiation.

@ On the other hand saying not overriding is allowed

  1        generic
  2                type GT is tagged private;
  3        package GP is
  4                type NT is new GT with private;
  5                not overriding -- legal, GT has no Op
  6                        procedure Op(X: NT);
  7        private

@ However, in this case we cannot instantiate GP with a type that does have an operation Op because it would fail when checked on the instantiation. So in a sense this imposes a further contract on the generic. If we do not want to impose this restriction then we must not give an overriding indicator on the procedure Op for NT.

@ Another situation arises when the generic formal is derived

  1        generic
  2                type GT is new T with private;
  3        package GP is
  4                type NT is new GT with private;
  5                overriding -- legal if T has Op
  6                        procedure Op (X : NT);
  7        private

@ In this case it might be that the type T does have an operation Op in which case we can give the overriding indicator.

@ We might also try

  1        generic
  2                type GT is tagged private;
  3                with procedure Op (X : GT);
  4        package GP is
  5                type NT is new GT with private;
  6                overriding -- illegal, Op not primitive
  7                        procedure Op (X : NT);
  8        private

@ But this is incorrect because although GT has to have an operation corresponding to Op as specified in the formal parameter list, nevertheless it does not have to be a primitive operation nor does it have to be called Op and thus it isn't inherited.

@ It should also be observed that overriding indicators can be used with untagged types although they have been introduced primarily to avoid problems with dispatching operations. Consider

  1        package P is
  2                type T is private;
  3                function "+" (Left, Right : T) return T;
  4        private
  5                type T is range 0 .. 100;    -- "+" overrides
  6        end P;

@ is opposed to

  1        package P is
  2                type T is private;
  3                function "+" (Left, Right : T) return T;
  4        private
  5                type T is (Red, White, Blue);   -- "+" does not override
  6        end P;

@ The point is that the partial view does not reveal whether overriding occurs or not – nor should it since either implementation ought to be acceptable. We should therefore remain silent regarding overriding in the partial view. This is similar to the private extension and generic cases discussed earlier. Inserting overriding would be illegal on both examples, while not overriding would be allowed only on the second one (which would constrain the implementation as in the previous examples). Again, it is permissible to put an overriding indicator on the body of "+" to indicate whether or not it does override.

@ It is also possible for a subprogram to be primitive for more than one type (this cannot happen for more than one tagged type but it can happen for untagged types or one tagged type and some untagged types). It could then be overriding for some types and not overriding for others. In such a case it is considered to be overriding as a whole and any indicator should reflect this.

@ The possibility of having a pragma which would enforce the use of overriding indicators (so that they too could not be inadvertently omitted) was eventually abandoned largely because of the private type and generic problem which made the topic very complicated.

@ Note the recommended layout, an overriding indicator should be placed on the line before the subprogram specification and aligned with it. This avoids disturbing the layout of the specification.

@ It is hoped that programmers will use overriding indicators freely. As mentioned in the Introduction, they are very valuable for preventing nasty errors during maintenance. Thus if we add a further parameter to an operation such as Op for a root type and all type extensions have overriding indicators then the compiler will report an error if we do not modify the operators of all the derived types correctly.

@ We now turn to a minor change in the overriding rules for functions with controlling results.

@ The reader may recall the general rule in Ada 95 that a function that is a primitive operation of a tagged type and returns a value of the type, must always be overridden when the type is extended. This is because the function for the extended type must create values for the additional components. This rule is sometimes phrased as saying that the function "goes abstract" and so has to be overridden if the extended type is concrete. The irritating thing about the rule in Ada 95 is that it applies even if there are no additional components.

@ Thus consider a generic version of the set package of Section 3

  1        generic
  2                type Element is private;
  3        package Sets is
  4                type Set is tagged private;
  5                function Empty return Set;
  6                function Unit (E : Element) return Set;
  7                function Union (S, T : Set) return Set;
  8                function Intersection (S, T : Set) return Set;
  9                ...
 10        end Sets;

@ Now suppose we declare an instantiation thus

  1        package My_Sets is new Sets (My_Type);

@ This results in the type Set and all its operations being declared inside the package My_Sets. However, for various reasons we might wish to have the type and its operations at the current scope. One reason could just be for simplicity of naming so that we do not have to write My_Sets.Set and My_Sets.Union and so on. (We might be in a regime where use clauses are forbidden.) An obvious approach is to derive our own type locally so that we have

  1        package My_Sets is new Sets (My_Type);
  2        type My_Set is new My_Sets.Set with null record;

@ Another situation where we might need to do this is where we wish to use the type Set as the full type for a private type thus

  1                type My_Set is private;
  2        private
  3                package My_Sets is new Sets(My_Type);
  4                type My_Set is new My_Sets.Set with null record;

@ But this doesn't work nicely in Ada 95 since all the functions have controlling results and so "go abstract" and therefore have to be overridden with wrappers thus

  1        function Union (S, T : My_Set) return My_Set is
  2        begin
  3                return My_Set (My_Sets.Union (My_Sets.Set (S), My_Sets.Set (T)));
  4        end Union;

@ This is clearly a dreadful nuisance. Ada 2005 sensibly allows the functions to be inherited provided that the extension is visibly null (and that there is no new discriminant part) and so no overriding is required. This new facility will be much appreciated by users of the new container library in Ada 2005 which has just this style of generic packages which export tagged types.

@ The final topic to be discussed concerns a problem with overloading and untagged types. Remember that the concept of abstract subprograms was introduced into Ada 95 largely for the purpose of tagged types. However it can also be used with untagged types on derivation if we do not want an operation to be inherited. This often happens with types representing physical measurements.

@ Consider

  1        type Length is new Float;
  2        type Area is new Float;

@ These types inherit various undesirable operations such as multiplying a length by a length to give a length when of course we want an area. We can overcome this by overriding them with abstract operations. Thus

  1        function "*" (L, R : Length) return Length is abstract;
  2        function "*" (L, R : Area) return Area is abstract;
  3        function "*" (L, R : Length) return Area;

@ We have also declared a function to multiply two lengths to give an area. So now we have two functions multiplying two lengths, one returns a length but is abstract and so can never be called and the other correctly returns an area.

@ Now suppose we want to print out some values of these types. We might declare a couple of functions delivering a string image thus

  1        function Image (L : Length) return String;
  2        function Image (L : Area) return String;

@ And then we decide to write

  1        X : Length := 2.5;
  2        ...
  3        Put_Line (Image (X*X)); -- ambiguous in 95

@ This fails to compile in Ada 95 since it is ambiguous because both Image and "*" are overloaded. The problem is that although the function "*" returning a length is abstract it nevertheless is still there and is considered for overload resolution. So we don't know whether we are calling Image on a length or on an area because we don't know which "*" is involved.

@ So declaring the operation as abstract does not really get rid of the operation at all, it just prevents it from being called but its ghost lives on and is a nuisance.

@ In Ada 2005 this is overcome by a new rule that says "abstract nondispatching subprograms are ignored during overload resolution". So the abstract "*" is ignored and there is no ambiguity in Ada 2005.

@ Note that this rule does not apply to dispatching operations of tagged types since we might want to dispatch to a concrete operation of a descendant type. But it does apply to operations of a class-wide type.

Rationale for Ada 2005: Object oriented model

@ENGRUSTOPBACKNEXT

7. Замена (overriding) и перегрузка (overloading)

@ Одна из главных заявленых целей Ады - поощрение написания правильных программ. Структура языка, строгий контроль типов, и так далее должны были гарантировать, что большинство ошибок, которые не обнаруживаются большинством языков, должны быть обнаружены во время компиляции. К сожалению, введение расширения типов и замены (overriding) в Аде 95 создало ситуацию, когда небрежные ошибки в конфигурации подпрограммы приводят к ошибкам, которые теперь невозможно обнаружить.

@ Введение описало два типичных примера. Первая проблема - процедура Finalize.

  1        with Ada.Finalization; use Ada.Finalization;
  2        package Root is
  3                type T is new Controlled with ... ;
  4                procedure Op (Obj : in out T; Data : in Integer);
  5                procedure Finalise (Obj : in out T);
  6        end Root;

@ Мы неосторожно написали Finalise вместо Finalize. Это означает, что Finalize не переопределяется как ожидается и ожидаемое поведение при завершении объектов типа T не происходит.

@ В Аде 2005 мы можем указать префикс overriding в объявлении:

  1        overriding procedure Finalize (Obj : in out T);

@ И теперь если мы неосторожно напишем Finalise, то это будет обнаружено при компиляции.

@ Подобные ошибки могут произойти в конфигурации. Если мы пишем:

  1        package Root.Leaf is
  2                type NT is new T with null record;
  3                overriding -- overriding indicator
  4                        procedure Op (Obj : in out NT; Data : in String);
  5                end Root.Leaf;

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

@ Однако, если мы действительно хотим добавить новую операцию тогда, мы можем написать:

  1        not overriding procedure Op (Obj : in out NT; Data : in String);

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

  1        overriding procedure Pap (X : TT) is abstract;
  2        overriding procedure Pep (X : TT) is null;
  3        overriding procedure Pip (Y : TT) renames Pop;
  4        not overriding procedure Poop is new Peep ( ... );
  5        overriding procedure Pup (Z : TT) is separate;
  6        overriding procedure Pup (X : TT) is begin ... end Pup;

@ Мы не должны применять индикатор замены и к спецификации процедуры и к телу, но если мы это делаем тогда, они естественно, не должны находиться в противоречии. Ожидается, что индикаторы замены будут обычно указываться в спецификациях, но они были бы уместными в случае тела без спецификации, как в примере Action в предыдущем разделе. Таким образом, мы могли бы иметь:

  1        procedure Green_To_Red (L : in List) is
  2                type GTR_It is new Iterator with null record;
  3                overriding procedure Action (It : in out GTR_It; C : in out Colour) is
  4                begin
  5                        if C = Green then C := Red; end if;
  6                end Action;
  7                ...

@ Индикаторы замены являются опциональными по двум причинам. Первое - просто для совместимости с Адой 95. Второе - некоторые проблемы с приватными типами и порождающими средствами.

@ Рассмотрим:

  1        package P is
  2                type NT is new T with private;
  3                procedure Op (X : T);
  4        private

@ Теперь предположим, что у типа T нет операции Op. Тогда было бы неправильно написать:

  1        package P is
  2                type NT is new T with private;     -- T has no Op
  3                overriding -- illegal
  4                        procedure Op (X : T);
  5        private

@ потому что это противоречило бы информации, известной в частичном представлении.

@ Но предположим, что фактически оказывается, что в частной части тип NT фактически получен из TT (непосредственно унаследованный из T) и что у TT действительно есть операция Op.

  1        private
  2                type NT is new TT with ... -- TT has Op
  3        end P;

@ В таком случае оказывается, что Op фактически заменяется в конце концов. В этом случае мы можем поместить индикатор overriding в тело Op, т.к. мы действительно знаем, что это замена.

@ Конечно мы не должны определять not overriding для Op в видимой части, потому что это не может бы быть истиной всегда (так как может случиться так, что у TT действительно есть Op). Однако, если мы поместим not overriding в частичное представление (что само по себе не является ошибкой), но если полное представление содержит not overriding оно, таким образом, гарантирует, что у TT нет Op.

@ Конечно, если у самого T есть Op тогда мы могли бы поместить индикатор overriding в видимую часть, так как мы знаем что это так.

@ Общее правило состоит в том, чтобы не лгать. Но правила являются немного разными для overriding и для not overriding. Для того, чтобы указать overriding это не должно быть неправдой в соответствующем пункте. Для указания not overriding это не должно входить в противоречие где-нибудь.

@ Эта асимметрия немного походит на предположение, что заключенный невинен, пока не оказался виновным. Мы иногда запускаем с целью, в котором операция, кажется, не отменяет, и затем позже мы находим, что она отменяет в конце концов. Но перемена никогда не случается - мы никогда не запускаем с целью, в котором она отменяет и затем позже обнаруживать, что этого не было. Таким образом, асимметрия реальна и выровнена.

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

@ Подобные проблемы возникают и с порождающими средствами. Как обычно с порождающими средствами, правила проверяются в generic непосредственно и затем повторно проверяются после реализации (в этом случае для использования и в пределах видимой части и в пределах частной части спецификации). Рассмотрим:

  1        generic
  2                type GT is tagged private;
  3        package GP is
  4                type NT is new GT with private;
  5                overriding -- illegal, GT has no Op
  6                        procedure Op (X : NT);
  7        private

@ Это должно быть незаконно, потому что у GT нет никакой операции Op. Конечно у фактического типа при реализации мог бы быть Op, но проверка должна пройти и в generic и в реализации.

@ С другой стороны высказывание not overriding позволено

  1        generic
  2                type GT is tagged private;
  3        package GP is
  4                type NT is new GT with private;
  5                not overriding -- legal, GT has no Op
  6                        procedure Op(X: NT);
  7        private

@ Однако, в этом случае мы не можем проиллюстрировать GP с типом, у которого действительно есть операция Op, потому что он подвел бы при проверерке реализации. Таким образом в некотором смысле это налагает дальнейшее ограничение на generic. Если мы не хотим ввести это ограничение тогда, мы не должны указыватьь индикатор overriding процедуре Op для NT.

@ Другая ситуация возникает, когда generic формально наследуется:

  1        generic
  2                type GT is new T with private;
  3        package GP is
  4                type NT is new GT with private;
  5                overriding -- legal if T has Op
  6                        procedure Op (X : NT);
  7        private

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

@ Мы могли бы также попробовать

  1        generic
  2                type GT is tagged private;
  3                with procedure Op (X : GT);
  4        package GP is
  5                type NT is new GT with private;
  6                overriding -- illegal, Op not primitive
  7                        procedure Op (X : NT);
  8        private

@ Но это является неправильным, потому что, хотя у GT должна быть операция, соответствующая Op как определено в формальном списке параметров, однако это не должна быть примитивная операция, и при этом это нельзя назвать Op, и таким образом это не унаследовано.

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

  1        package P is
  2                type T is private;
  3                function "+" (Left, Right : T) return T;
  4        private
  5                type T is range 0 .. 100;    -- "+" overrides
  6        end P;
  1        package P is
  2                type T is private;
  3                function "+" (Left, Right : T) return T;
  4        private
  5                type T is (Red, White, Blue);   -- "+" does not override
  6        end P;

@ Дело в том, что частичное представление не показывает, происходит отмена или нет - ни если это начиная с любой реализации должно быть приемлемо. Мы должны поэтому относится осторожно к отмене в частичном представлении. Это подобно частному расширению и generic случаям, обсуждаемым ранее. Вставка слова overriding была бы незаконна в обоих примерах, в то время как not overriding будет позволена только на втором (который сдержал бы реализацию как в предыдущих примерах). Снова, допустимо поместить индикатор overriding в тело "+", чтобы указать, отменяет ли это действительно.

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

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

@ Отметим рекомендуемую схему размещения, индикатор отмены должен быть помещен в строку перед спецификацией подпрограммы и выровнен с нею. Это не нарушаеть схему размещения спецификации.

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

@ Мы теперь обращаемся к незначительному изменению в правилах отмены для функций с управлением результатами.

@ Читатель может напомнить общее правило в Аде 95, что функция, которая является примитивной операцией тегового типа и возвращает значение типа, должна всегда отменяться при расширении типа. Это потому что функция для расширенного типа должна создавать значение для дополнительных компонентов. Это правило иногда выражается утверждением, что функция являющейся абстрактной и так должна быть заменена, если расширенный тип конкретен. Раздражающая вещь этого правила в Аде 95 состоит в том, что это оно применяется, даже если нет никаких дополнительных компонентов.

@ Таким образом, рассмотрим универсальную версию пакета из Раздела 3:

  1        generic
  2                type Element is private;
  3        package Sets is
  4                type Set is tagged private;
  5                function Empty return Set;
  6                function Unit (E : Element) return Set;
  7                function Union (S, T : Set) return Set;
  8                function Intersection (S, T : Set) return Set;
  9                ...
 10        end Sets;

@ Теперь предположим, что мы объявляем реализацию таким образом:

  1        package My_Sets is new Sets (My_Type);

@ Это помещает тип Set со всеми его операциям в пакет My_Sets. Однако, по различным причинам мы могли бы желать иметь тип и его операции в текущей области видимости. Например, для простоты обозначения, чтобы мы не писали My_Sets.Set и My_Sets.Union и так далее. (Мы могли бы быть в ситуации, когда USE-выражения запрещены). Очевидный подход должен получить наш собственный тип в местном масштабе так, чтобы мы имели:

  1        package My_Sets is new Sets (My_Type);
  2        type My_Set is new My_Sets.Set with null record;

@ Другая ситуация, где мы, возможно, должны сделать это - ситуация когда мы желаем сделать тип Set частным:

  1                type My_Set is private;
  2        private
  3                package My_Sets is new Sets (My_Type);
  4                type My_Set is new My_Sets.Set with null record;

@ Но это не работает нормально в Аде 95, так как у всех функций есть управляющие результаты и таким образом "go abstract" и поэтому должны быть заменены с обертками таким образом

  1        function Union (S, T : My_Set) return My_Set is
  2        begin
  3                return My_Set (My_Sets.Union (My_Sets.Set (S), My_Sets.Set (T)));
  4        end Union;

@ Это - ясно ужасная неприятность. Ада 2005 заметно позволяет функциям быть наследованными при условии, что расширение явно нулевое (и что нет никакой новой дискриминантной части) и так никакая отмена не требуется. Это новое средство будет очень цениться пользователями новой контейнерной библиотеки в Аде 2005, у которого есть только этот стиль универсальных пакетов, которые экспортируют теговые типы.

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

@ Рассмотрим:

  1        type Length is new Float;
  2        type Area is new Float;

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

  1        function "*" (L, R : Length) return Length is abstract;
  2        function "*" (L, R : Area) return Area is abstract;
  3        function "*" (L, R : Length) return Area;

@ Мы также добавили функцию умножения двух длин возвращающую площадь. Так теперь у нас есть две абстрактные функции, умножающие две длины и возвращающие длину которые никогда не могут вызыватьс и одна функция правильно возвращяющая площадь.

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

  1        function Image (L : Length) return String;
  2        function Image (L : Area) return String;

@ И затем мы решаем написать:

  1        X : Length := 2.5;
  2        ...
  3        Put_Line (Image (X*X)); -- ambiguous in 95

@ Это не может компилироваться на Аде 95, так остаётся неоднозначность несмотря на то что Image и "*" перегружены. Проблема состоит в том, что, хотя функция "*" возвращяющая длину абстрактна, она однако все еще рассматривается как кандидат для перегрузки. Таким образом, мы не знаем, вызываем ли мы Image для длины или для области, потому что мы не знаем, который из "*" вовлечен.

@ Объявляя операцию с атрибутом abstaract мы в действительности не избавляется от операции вообще, это только препятствует её непосредственному вызову, но ее призрачные жизни являются неприятностью.

@ На Аде 2005 эта проблема решается по новому правилу, которое говорит, что "абстрактные подпрограммы недиспетчеризации игнорируютс во время разрешающей способности перегрузки". Таким образом, на Аде 2005 в приведённом примере abstract "*" игнорируется и нет никакой двусмысленности.

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

@ ENG RUS

TOP BACK NEXT

2010-10-24 00:26:53

Информационные стойки, торговые стенды цены в Москве. . .