type PERSON(SEX : GENDER := F) is record AGE : INTEGER range 0 .. 123; ... end record; type PERSON_NAME is access PERSON; |
In this example, PERSON is declared as a record type, and static variables of this type can be declared as usual. The access type PERSON_NAME is declared as a type whose values provide access to dynamically allocated record objects of type PERSON.
It is of course possible to copy the value of a dynamically allocated PERSON into a static variable of this type and vice versa. Note, however, that there is no way for an access variable of type PERSON_NAME to designate a static variable of type PERSON.
The type of the dynamically allocated objects can be any type. For example it can be an array type, as in
type ALPHA is access STRING;
It is possible to declare a subtype of an access type, and this will mean that the constraints defined by the subtype declaration are imposed on the dynamically allocated objects. Thus the subtype ALPHA_LINE defined below corresponds to dynamically allocated strings of 80 characters:
subtype ALPHA_LINE is ALPHA(1 .. 80);
Collections in Ada are implicit and cannot be named (unlike those in Lis and Euclid). The collections associated with different access types are always disjoint, so that dynamically allocated objects designated by access variables that do not have the same type are guaranteed to be in different collections.
YOU, HIM, HER : PERSON_NAME; -- implicit initialization to null SOMEONE : PERSON_NAME := null; -- explicit initialization to null |
An allocator creates a dynamically allocated object and assigns its internal name to an access variable:
YOU := new PERSON'(SEX => F, AGE => 30, ... ); -- all components, as usual
The above allocator includes a qualified aggregate, with the name of the type of the dynamically allocated object - the so-called designated type - and with the aggregate defining the initial value of this object.
The constraints applicable to a dynamically allocated object are established when the allocator is evaluated and cannot be modified during the lifetime of the dynamically allocated object. In the case of a dynamically allocated array, this means that the bounds of such an array cannot be modified. Consider
MESSAGE : ALPHA := new STRING'(1 .. 45 => ' ');
It is certainly possible to modify the character values of the string designated by MESSAGE, but the bounds of this string remain those that are set at allocation time (here 1 and 45). Similarly, for a type with discriminants, the discriminant values established at allocation time cannot be modified:
type TEXT(SIZE : LENGTH) is record POS : LENGTH := 0; DATA : STRING(1 .. SIZE); end record; type TEXT_NAME is access TEXT; BUFFER : TEXT_NAME; ... BUFFER := new TEXT'(SIZE => 50, POS => 0, DATA => (1 .. 50 => '*')); |
The discriminant SIZE, once initialized by the allocator, cannot be changed thereafter (not even by a whole record assignment to the dynamically allocated record object). As a consequence, only the size actually required by the dynamically allocated object need be allocated.
Another possibility is to provide a constraint in the allocator without otherwise initializing the dynamically allocated object. For a discriminant constraint, the corresponding discriminants are initialized. Examples of such allocators are given below:
MESSAGE := new STRING(1 .. 90); -- index constraint HIM := new PERSON(SEX => M); -- discriminant constraint BUFFER := new TEXT(SIZE => 40); -- discriminant constraint |
Declarations of access constants are given in the usual way. The access value (an internal name) contained by an access constant cannot be changed. Consider, for example, the constant declarations:
YOU_NOW : constant PERSON_NAME := YOU; DAY_NAME : constant array (1 .. 7) of ALPHA := (new STRING'("MONDAY"), new STRING'("TUESDAY"), new STRING'("WEDNESDAY"), new STRING'("THURSDAY"), new STRING'("FRIDAY"), new STRING'("SATURDAY"), new STRING'("SUNDAY") ); |
The constant YOU_NOW contains the internal name of the dynamically allocated record designated by YOU at the time of the initialization. It means that YOU_NOW will always contain this access value even if YOU is updated at a later time. On the other hand, components of the person designated by this constant can be modified (aside from the discriminant) by assignments such as
YOU_NOW.AGE := 31; -- or indirectly by YOU.AGE := 31; |
Similarly, the array DAY_NAME is a constant array, hence its components are constant access values obtained from allocators. But this does not mean that the strings designated by these constants are themselves constant, and it would not be possible for a compiler to perform the string allocations statically (at compilation time) unless their invariability can be deduced on other grounds: for example, if this array were local to a package body in which it is read but never updated.
YOU.AGE -- a component that has the type INTEGER YOU.SEX -- a component that has the type GENDER |
Similarly, we can use the normal selection syntax to designate the entire (dynamically allocated) record object. Thus YOU.all is an object of type PERSON such that the following conditions are true:
YOU.all.SEX = YOU.SEX YOU.all.AGE = YOU.AGE ... |
This notation can also appear in an allocator, as in the assignment statement
HER := new PERSON'(YOU.all);
Finally the same notation may be used for value assignments. Remember that if YOU and HER contain internal names of dynamically allocated record objects, then after the assignment
YOU := HER;
the two access variables contain the same internal name. In contrast, the value assignment for copying the value of the dynamically allocated record designated by HER into the dynamically allocated record designated by YOU - without necessarily altering the access values - is written
YOU.all := HER.all;
Such value assignments are always possible between dynamically allocated record objects without variants. With variants, they are legal only if the discriminants of the objects are identical. This must be checked (usually at execution time), and the exception CONSTRAINT_ERROR is raised if the check fails.
Indexed components for arrays denoted by access types are written exactly as in the case of statically denoted arrays (this means that dereferencing is also implicit for indexing). Thus we can write
MESSAGE(1) := '*'; MESSAGE(11 .. 16) := DAY_NAME(1)(1..6); MESSAGE(21 .. 27) := "MORNING"; |
Note, finally, that the notation X.all, denoting the dynamically allocated object designated by X, can be used for all dynamically allocated objects, whether they are records, arrays, scalars, or task objects.
type PERSON(SEX : GENDER := F); -- Incomplete declaration of -- PERSON (1) type PERSON_NAME is access PERSON; -- Access type declaration (2) type PERSON(SEX : GENDER := F) is -- Full declaration of PERSON (3) record AGE : INTEGER range 0 .. 123; FATHER : PERSON_NAME(SEX => M); -- Component declaration (4) MOTHER : PERSON_NAME(SEX => F); -- Component declaration (5) SPOUSE : PERSON_NAME; -- Component declaration (6) ... end record; |
The incomplete declaration allows a linear reading of the example: We first learn about the existence of a type called PERSON, so that at (2) we can understand what "access PERSON" means. We then learn what the type PERSON is in full. Without the incomplete declaration (1), the access type declaration (2) would be illegal. Similarly, we could not reverse the order of declarations (2) and (3) because then (3) would be illegal: we need to know what a PERSON_NAME is in order to understand the component declarations at (4), (5), and (6).
Having declared objects of this type, we can establish relations between them, and these relations can evolve dynamically. For example
HENRY_VIII : PERSON_NAME(M) := new PERSON(SEX => M); ANNE_BOLEYN : PERSON_NAME(F) := new PERSON(SEX => F); JANE_SEYMOUR : PERSON_NAME(F) := new PERSON(SEX => F); ... HENRY_VIII.SPOUSE := ANNE_BOLEYN; ANNE_BOLEYN.SPOUSE := HENRY_VIII; ... HENRY_VIII.SPOUSE := JANE_SEYMOUR; JANE_SEYMOUR.SPOUSE := HENRY_VIII; |
Note in particular that such recursive structures may include cycles: for example
HENRY_VIII.SPOUSE.SPOUSE
designates the same object as the access variable
HENRY_VIII
itself.
This kind of recursion in access type declarations may involve more than one access type. In such cases it is necessary to provide an incomplete declaration for each type whose name is mentioned before the occurrence of its full declaration. This is shown by the following pair of access types:
type CAR; -- Incomplete declaration of CAR type PERSON(SEX : GENDER := F); -- Incomplete declaration of PERSON type CAR_NAME is access CAR; type PERSON_NAME is access PERSON; type CAR is record OWNER : PERSON_NAME; SERIAL_NUMBER : POSITIVE; end record; type PERSON(SEX : GENDER := F) is record ... VEHICLE : CAR_NAME; ... end record; |
type PLACE; type LIST is access PLACE; type PLACE is record SUCC, PRED : LIST; CONTENT : ITEM; end record; |
A function CARDINAL that counts the elements in a given circular list can be written as follows:
function CARDINAL(HEAD : LIST) return NATURAL is -- The head is not counted as a list element -- For an empty list, HEAD.SUCC = HEAD.PRED = HEAD NEXT : ITEM := HEAD.SUCC; COUNT : NATURAL := 0; begin while NEXT /= HEAD loop NEXT := NEXT.SUCC; COUNT := COUNT +1; end loop; return COUNT; end; |
Moreover, assignment to the object designated by an in parameter, or to a component of that object, is also permitted.
As an example, consider the procedure given below:
procedure DIVORCE(P : in PERSON_NAME) is begin P.SPOUSE.SPOUSE := null; P.SPOUSE := null; end; |
Although P is an in parameter, assignment to P.SPOUSE is permitted.
for CAR_NAME'STORAGE_SIZE use -- no more than 2000 cars (2000*CAR'SIZE) / SYSTEM.STORAGE_UNIT; |
The expression provided after the reserved word use is the size in storage units of the storage area to be reserved for the collection of dynamically allocated cars designated by values of the type CAR_NAME. Given an estimate of the maximum number of cars to be allocated (here 2000), the size in bits is obtained by multiplying this number by the value of the attribute CAR'SIZE; the size in storage units is then obtained by dividing the result by the size in bits of a storage unit (SYSTEM.STORAGE_UNIT). Note that this storage area does not limit the storage for persons designated by values of the type PERSON_NAME, in spite of the fact that each CAR has a component of this type.
A collection for which such a length clause has been given behaves like a static array as far as storage allocation is concerned. The objects are allocated within this static storage area by allocators; and the whole collection can be reclaimed globally under the same conditions as for an array declared at the place of the access type declaration. The exception STORAGE_ERROR is raised if the space reserved is insufficient for an allocation.
If we want to ensure that garbage collection is never performed by the run-time system, the following pragma must be used
pragma CONTROLLED(CAR_NAME);
Such collections may be allocated either on the stack or on the heap. They have several advantages. In terms of storage management they have a cost comparable to that of arrays. In addition they offer both the notational advantages and the addressing efficiency of access variables. Finally, if an application wants to perform its own deallocation, it can do so by means of a generic instantiation of a predefined generic library procedure, as follows:
procedure FREE is new UNCHECKED_DEALLOCATION(OBJECT => CAR, NAME => CAR_NAME); |
The resulting procedure FREE has a parameter profile corresponding to the following specification:
procedure FREE(X : in out CAR_NAME);
The execution of a call such as FREE(MY_CAR); will assign the null value to MY_CAR, and establish that the storage occupied by the object designated by MY_CAR can be reclaimed. This form of deallocation is said to be unchecked since no check will then be done to ensure that there are no dangling accesses to the same object. The use of this form of deallocation may therefore be justified by efficiency, but it presents some danger, and so programs that use it must be written with great care.