Contents Index Search Previous Next
7.6 User-Defined Assignment and Finalization
1
[
{user-defined assignment}
{assignment (user-defined)}
Three kinds of actions are fundamental to the manipulation
of objects: initialization, finalization, and assignment. Every object
is initialized, either explicitly or by default, after being created
(for example, by an
object_declaration
or
allocator). Every object is finalized
before being destroyed (for example, by leaving a
subprogram_body
containing an
object_declaration,
or by a call to an instance of Unchecked_Deallocation). An assignment
operation is used as part of
assignment_statements,
explicit initialization, parameter passing, and other operations.
{constructor:
See initialization} {constructor:
See Initialize} {destructor:
See finalization}
2
Default definitions for these three fundamental
operations are provided by the language, but
{controlled
type} a
controlled type gives the
user additional control over parts of these operations.
{Initialize}
{Finalize} {Adjust}
In particular, the user can define, for a controlled
type, an Initialize procedure which is invoked immediately after the
normal default initialization of a controlled object, a Finalize procedure
which is invoked immediately before finalization of any of the components
of a controlled object, and an Adjust procedure which is invoked as the
last step of an assignment to a (nonlimited) controlled object.]
2.a
Glossary entry: {Controlled
type} A controlled type supports user-defined assignment and finalization.
Objects are always finalized before being destroyed.
2.b
Ramification: Here's
the basic idea of initialization, value adjustment, and finalization,
whether or not user defined: When an object is created, if it is explicitly
assigned an initial value, the assignment copies and adjusts the initial
value. Otherwise, Initialize is applied to it (except in the case of
an aggregate as a whole). An assignment_statement
finalizes the target before copying in and adjusting the new value. Whenever
an object goes away, it is finalized. Calls on Initialize and Adjust
happen bottom-up; that is, components first, followed by the containing
object. Calls on Finalize happens top-down; that is, first the containing
object, and then its components. These ordering rules ensure that any
components will be in a well-defined state when Initialize, Adjust, or
Finalize is applied to the containing object.
Static Semantics
3
The following language-defined
library package exists:
4/1
{8652/0020}
package Ada.Finalization is
pragma Preelaborate(Finalization);
pragma Remote_Types(Finalization);
5
type Controlled is abstract tagged private;
6
procedure Initialize (Object : in out Controlled);
procedure Adjust (Object : in out Controlled);
procedure Finalize (Object : in out Controlled);
7
type Limited_Controlled is abstract tagged limited private;
8
procedure Initialize (Object : in out Limited_Controlled);
procedure Finalize (Object : in out Limited_Controlled);
private
... -- not specified by the language
end Ada.Finalization;
9
{controlled type}
A controlled type is a descendant of Controlled or
Limited_Controlled. The (default) implementations of Initialize, Adjust,
and Finalize have no effect. The predefined "=" operator of
type Controlled always returns True, [since this operator is incorporated
into the implementation of the predefined equality operator of types
derived from Controlled, as explained in
4.5.2.]
The type Limited_Controlled is like Controlled, except that it is limited
and it lacks the primitive subprogram Adjust.
9.a
Discussion: We say ``nonlimited
controlled type'' (rather than just ``controlled type''; when we
want to talk about descendants of Controlled only.
9.b
Reason: We considered
making Adjust and Finalize abstract. However, a reasonable coding convention
is e.g. for Finalize to always call the parent's Finalize after doing
whatever work is needed for the extension part. (Unlike CLOS, we have
no way to do that automatically in Ada 95.) For this to work, Finalize
cannot be abstract. In a generic unit, for a generic formal abstract
derived type whose ancestor is Controlled or Limited_Controlled, calling
the ancestor's Finalize would be illegal if it were abstract, even though
the actual type might have a concrete version.
9.c
Types Controlled and Limited_Controlled
are abstract, even though they have no abstract primitive subprograms.
It is not clear that they need to be abstract, but there seems to be
no harm in it, and it might make an implementation's life easier to know
that there are no objects of these types -- in case the implementation
wishes to make them ``magic'' in some way.
Dynamic Semantics
10
{elaboration (object_declaration)
[partial]} During the elaboration of an
object_declaration,
for every controlled subcomponent of the object that is not assigned
an initial value (as defined in
3.3.1), Initialize
is called on that subcomponent. Similarly, if the object as a whole is
controlled and is not assigned an initial value, Initialize is called
on the object. The same applies to the evaluation of an
allocator,
as explained in
4.8.
11/1
{
8652/0021}
For an
extension_aggregate whose
ancestor_part is a
subtype_mark,
for each controlled subcomponent of the ancestor part, either Initialize
is called, or its initial value is assigned, as appropriate Initialize
is called on all controlled subcomponents of the ancestor part; if
the type of the ancestor part is itself controlled, the Initialize procedure
of the ancestor type is called, unless that Initialize procedure is abstract.
11.a
Discussion:
Example:
11.b
type T1 is new Controlled with
record
... -- some components might have defaults
end record;
11.c
type T2 is new Controlled with
record
X : T1; -- no default
Y : T1 := ...; -- default
end record;
11.d
A : T2;
B : T2 := ...;
11.e
As part of the elaboration of
A's declaration, A.Y is assigned a value; therefore Initialize is not
applied to A.Y. Instead, Adjust is applied to A.Y as part of the assignment
operation. Initialize is applied to A.X and to A, since those objects
are not assigned an initial value. The assignment to A.Y is not considered
an assignment to A.
11.f
For the elaboration of B's declaration,
Initialize is not called at all. Instead the assignment adjusts B's value;
that is, it applies Adjust to B.X, B.Y, and B.
11.f.1/1
{8652/0021}
The ancestor_part of an extension_aggregate
is handled similarly.
12
Initialize and other initialization operations
are done in an arbitrary order, except as follows. Initialize is applied
to an object after initialization of its subcomponents, if any [(including
both implicit initialization and Initialize calls)]. If an object has
a component with an access discriminant constrained by a per-object expression,
Initialize is applied to this component after any components that do
not have such discriminants. For an object with several components with
such a discriminant, Initialize is applied to them in order of their
component_declarations. For an allocator,
any task activations follow all calls on Initialize.
12.a
Reason: The fact that
Initialize is done for subcomponents first allows Initialize for a composite
object to refer to its subcomponents knowing they have been properly
initialized.
12.b
The fact that Initialize is
done for components with access discriminants after other components
allows the Initialize operation for a component with a self-referential
access discriminant to assume that other components of the enclosing
object have already been properly initialized. For multiple such components,
it allows some predictability.
13
{assignment
operation} When a target object with any
controlled parts is assigned a value, [either when created or in a subsequent
assignment_statement,] the
assignment
operation proceeds as follows:
14
- The value of the target becomes the
assigned value.
15
- {adjusting the
value of an object} {adjustment}
The value of the target is adjusted.
15.a
Ramification: If any
parts of the object are controlled, abort is deferred during the assignment
operation.
16
{adjusting the value of an
object} {adjustment}
To adjust the value of a [(nonlimited)] composite
object, the values of the components of the object are first adjusted
in an arbitrary order, and then, if the object is controlled, Adjust
is called. Adjusting the value of an elementary object has no effect[,
nor does adjusting the value of a composite object with no controlled
parts.]
16.a
Ramification: Adjustment
is never performed for values of a by-reference limited type, since these
types do not support copying.
16.b
Reason: The verbiage
in the Initialize rule about access discriminants constrained by per-object
expressions is not necessary here, since such types are limited, and
therefore are never adjusted.
17
{execution (assignment_statement)
[partial]} For an
assignment_statement,
[ after the
name and
expression
have been evaluated, and any conversion (including constraint checking)
has been done,] an anonymous object is created, and the value is assigned
into it; [that is, the assignment operation is applied]. [(Assignment
includes value adjustment.)] The target of the
assignment_statement
is then finalized. The value of the anonymous object is then assigned
into the target of the
assignment_statement.
Finally, the anonymous object is finalized. [As explained below, the
implementation may eliminate the intermediate anonymous object, so this
description subsumes the one given in
5.2,
``
Assignment Statements''.]
17.a
Reason:
An alternative design for user-defined assignment might involve an
Assign operation instead of Adjust:
17.b
procedure Assign(Target : in out Controlled; Source : in out Controlled);
17.c
Or
perhaps even a syntax like this:
17.d
procedure ":="(Target : in out Controlled; Source : in out Controlled);
17.e
Assign
(or ":=") would have the responsibility of doing the copy,
as well as whatever else is necessary. This would have the advantage
that the Assign operation knows about both the target and the source
at the same time -- it would be possible to do things like reuse storage
belonging to the target, for example, which Adjust cannot do. However,
this sort of design would not work in the case of unconstrained discriminated
variables, because there is no way to change the discriminants individually.
For example:
17.f
type Mutable(D : Integer := 0) is
record
X : Array_Of_Controlled_Things(1..D);
case D is
when 17 => Y : Controlled_Thing;
when others => null;
end D;
end record;
17.g
An assignment to an unconstrained
variable of type Mutable can cause some of the components of X, and the
component Y, to appear and/or disappear. There is no way to write the
Assign operation to handle this sort of case.
17.h
Forbidding such cases is not
an option -- it would cause generic contract model violations.
Implementation Requirements
17.1/1
{
8652/0022}
For an aggregate of a controlled
type whose value is assigned, other than by an assignment_statement
or a return_statement, the implementation
shall not create a separate anonymous object for the aggregate.
The aggregate value shall be constructed directly in the target of the
assignment operation and Adjust is not called on the target object.
17.h.1/1
Reason: This is necessary
to prevent elaboration problems with deferred constants of controlled
types. Consider:
17.h.2/1
package P is
type Dyn_String is private;
Null_String : constant Dyn_String;
...
private
type Dyn_String is new Ada.Finalization.Controlled with ...
procedure Finalize(X : in out Dyn_String);
procedure Adjust(X : in out Dyn_String);
Null_String : constant Dyn_String :=
(Ada.Finalization.Controlled with ...);
...
end P;
17.h.3/1
When Null_String is elaborated,
the bodies of Finalize and Adjust clearly have not been elaborated. Without
this rule, this declaration would necessarily raise Program_Error (unless
the permissions given below are used by the compiler).
Implementation Permissions
18
An implementation is allowed to relax the above
rules [(for nonlimited controlled types)] in the following ways:
18.a
Proof: The phrase ``for
nonlimited controlled types'' follows from the fact that all of the following
permissions apply to cases involving assignment. It is important because
the programmer can count on a stricter semantics for limited controlled
types.
19
- For an assignment_statement
that assigns to an object the value of that same object, the implementation
need not do anything.
19.a
Ramification: In other
words, even if an object is controlled and a combination of Finalize
and Adjust on the object might have a net side effect, they need not
be performed.
20
- For an assignment_statement
for a noncontrolled type, the implementation may finalize and assign
each component of the variable separately (rather than finalizing the
entire variable and assigning the entire new value) unless a discriminant
of the variable is changed by the assignment.
20.a
Reason: For example,
in a slice assignment, an anonymous object is not necessary if the slice
is copied component-by-component in the right direction, since array
types are not controlled (although their components may be). Note that
the direction, and even the fact that it's a slice assignment, can in
general be determined only at run time.
21
- For an aggregate
or function call whose value is assigned into a target object, the implementation
need not create a separate anonymous object if it can safely create the
value of the aggregate or function
call directly in the target object. Similarly, for an assignment_statement,
the implementation need not create an anonymous object if the value being
assigned is the result of evaluating a name
denoting an object (the source object) whose storage cannot overlap with
the target. If the source object might overlap with the target object,
then the implementation can avoid the need for an intermediary anonymous
object by exercising one of the above permissions and perform the assignment
one component at a time (for an overlapping array assignment), or not
at all (for an assignment where the target and the source of the assignment
are the same object). Even if an anonymous object is created, the implementation
may move its value to the target object as part of the assignment without
re-adjusting so long as the anonymous object has no aliased subcomponents.
21.a
Ramification: In the
aggregate case, only one value adjustment
is necessary, and there is no anonymous object to be finalized.
21.b
In the assignment_statement
case as well, no finalization of the anonymous object is needed. On the
other hand, if the target has aliased subcomponents, then an adjustment
takes place directly on the target object as the last step of the assignment,
since some of the subcomponents may be self-referential or otherwise
position-dependent.
Extensions to Ada 83
21.c
{extensions to Ada 83}
Controlled types and user-defined finalization are
new to Ada 95. (Ada 83 had finalization semantics only for masters of
tasks.)
Contents Index Search Previous Next Legal