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/2
Ramification: {
AI95-00114-01}
{
AI95-00287-01}
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 object is either
built-in-place from an aggregate or function
call (in which case neither Adjust nor Initialize is applied), or 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
happen 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}
{
AI95-00126-01}
package Ada.Finalization
is
pragma Preelaborate(Finalization);
pragma Remote_Types(Finalization);
5/2
{
AI95-00161-01}
type Controlled
is abstract tagged private;
pragma Preelaborable_Initialization(Controlled);
6/2
{
AI95-00348-01}
procedure Initialize (Object :
in out Controlled)
is null;
procedure Adjust (Object :
in out Controlled)
is null;
procedure Finalize (Object :
in out Controlled)
is null;
7/2
{
AI95-00161-01}
type Limited_Controlled
is abstract tagged limited private;
pragma Preelaborable_Initialization(Limited_Controlled);
8/2
{
AI95-00348-01}
procedure Initialize (Object :
in out Limited_Controlled)
is null;
procedure Finalize (Object :
in out Limited_Controlled)
is null;
private
... --
not specified by the language
end Ada.Finalization;
9/2
{
AI95-00348-01}
{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.
9.d/2
{
AI95-00251-01}
For Ada 2005, we considered making these types
interfaces. That would have the advantage of allowing them to be added
to existing trees. But that was rejected both because it would cause
massive disruption to existing implementations, and because it would
be very incompatible due to the "no hidden interfaces" rule.
The latter rule would prevent a tagged private type from being completed
with a derivation from Controlled or Limited_Controlled — a very
common idiom.
9.1/2
{
AI95-00360-01}
A type is said to need finalization if:{needs
finalization} {type
(needs finalization)}
9.2/2
- it is a controlled
type, a task type or a protected type; or
9.3/2
- it has a component
that needs finalization; or
9.4/2
- it is a limited
type that has an access discriminant whose designated type needs finalization;
or
9.5/2
- it is one of
a number of language-defined types that are explicitly defined to need
finalization.
9.e/2
Ramification: The
fact that a type needs finalization does not require it to be implemented
with a controlled type. It just has to be recognized by the No_Nested_Finalization
restriction.
9.f/2
This property is defined
for the type, not for a particular view. That's necessary as restrictions
look in private parts to enforce their restrictions; the point is to
eliminate all controlled parts, not just ones that are visible.
Dynamic Semantics
10/2
{
AI95-00373-01}
{elaboration (object_declaration)
[partial]} During the elaboration
or
evaluation of a construct that causes an object to be initialized by
default 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
that
is initialized by default 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/2
{
8652/0021}
{
AI95-00182-01}
{
AI95-00373-01}
For an
extension_aggregate whose
ancestor_part
is a
subtype_mark denoting
a, 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
subtype, 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/2
{
8652/0021}
{
AI95-00182-01}
{
AI95-00373-01}
The ancestor_part of
an extension_aggregate,
<> in aggregates, and the return object of an extended_return_statement
are 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/2
{
8652/0022}
{
AI95-00083-01}
{
AI95-00318-02}
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/2
Reason:
{
AI95-00318-02}
{build-in-place
[partial]} This build-in-place requirement 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 implementation).
17.i/2
Ramification: An
aggregate used in the return expression of
a simple_return_statement has to be built-in-place
in the anonymous return object, as this is similar to an object declaration.
(This is a change from Ada 95, but it is not an inconsistency as it only
serves to restrict implementation choices.) But this only covers the
aggregate; a separate anonymous return object
can still be used unless it too is required to be built-in-place (see
7.5).
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/2
- {AI95-00147-01}
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/2
{
AI95-00147-01}
Similarly, in the function call case, the anonymous
object can be eliminated. Note, however, that Adjust must be called 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.
This Adjust can be eliminated only by using one of the following permissions.
22/2
{
AI95-00147-01}
Furthermore, an implementation is permitted to
omit implicit Initialize, Adjust, and Finalize calls and associated assignment
operations on an object of a nonlimited controlled type provided that:
23/2
- any omitted
Initialize call is not a call on a user-defined Initialize procedure,
and
23.a/2
To be honest: This
does not apply to any calls to a user-defined Initialize routine that
happen to occur in an Adjust or Finalize routine. It is intended that
it is never necessary to look inside of an Adjust or Finalize routine
to determine if the call can be omitted.
23.b/2
Reason: We don't
want to eliminate objects for which the Initialize might have side effects
(such as locking a resource).
24/2
- any usage of
the value of the object after the implicit Initialize or Adjust call
and before any subsequent Finalize call on the object does not change
the external effect of the program, and
25/2
- after the omission
of such calls and operations, any execution of the program that executes
an Initialize or Adjust call on an object or initializes an object by
an aggregate will also later execute a Finalize
call on the object and will always do so prior to assigning a new value
to the object, and
26/2
- the assignment
operations associated with omitted Adjust calls are also omitted.
27/2
This permission applies to
Adjust and Finalize calls even if the implicit calls have additional
external effects.
27.a/2
Reason: The goal
of the above permissions is to allow typical dead assignment and dead
variable removal algorithms to work for nonlimited controlled types.
We require that “pairs” of Initialize/Adjust/Finalize operations
are removed. (These aren't always pairs, which is why we talk about “any
execution of the program”.)
Extensions to Ada 83
27.b
{
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.)
Extensions to Ada 95
27.c/2
{
AI95-00161-01}
{extensions to Ada 95} Amendment
Correction: Types Controlled and Limited_Controlled now have Preelaborable_Initialization,
so that objects of types derived from these types can be used in preelaborated
packages.
Wording Changes from Ada 95
27.d/2
27.e/2
{
8652/0021}
{
AI95-00182-01}
Corrigendum: Added wording to clarify that
the default initialization (whatever it is) of an ancestor part is used.
27.f/2
{
8652/0022}
{
AI95-00083-01}
Corrigendum: Clarified that Adjust is never
called on an aggregate used for the initialization of an object or subaggregate,
or passed as a parameter.
27.g/2
{
AI95-00147-01}
Additional optimizations are allowed for nonlimited
controlled types. These allow traditional dead variable elimination to
be applied to such types.
27.h/2
{
AI95-00318-02}
Corrected the build-in-place requirement for controlled
aggregates to be consistent with the requirements
for limited types.
27.i/2
{
AI95-00348-01}
The operations of types Controlled and Limited_Controlled
are now declared as null procedures (see 6.7)
to make the semantics clear (and to provide a good example of what null
procedures can be used for).
27.j/2
27.k/2
{
AI95-00373-01}
Generalized the description of objects that have
Initialize called for them to say that it is done for all objects that
are initialized by default. This is needed so that all of the new cases
are covered.