7.6 Assignment and Finalization
[
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.
Default definitions for these three fundamental operations
are provided by the language, but
a
controlled
type gives the user additional control over parts of these operations.
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.]
Glossary entry: A controlled type supports
user-defined assignment and finalization. Objects are always finalized
before being destroyed.
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 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
The following language-defined
library package exists:
{
AI95-00161-01}
type Controlled
is abstract tagged private;
pragma Preelaborable_Initialization(Controlled);
{
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;
{
AI95-00161-01}
type Limited_Controlled
is abstract tagged limited private;
pragma Preelaborable_Initialization(Limited_Controlled);
{
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;
{
AI95-00348-01}
A controlled type is a descendant of Controlled or
Limited_Controlled. 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.
Discussion: We say “nonlimited
controlled type” (rather than just “controlled type”;)
when we want to talk about descendants of Controlled only.
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.
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.
{
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 disruptions 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.
it is a controlled type, a task type or a protected
type; or
{
AI05-0092-1}
it has a component whose type needs finalization; or
{
AI05-0026-1}
it is a partial view whose full view needs finalization; or
it is one of a number of language-defined types
that are explicitly defined to need finalization.
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.
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
{
AI95-00373-01}
During the elaboration or evaluation of a construct that causes an object
to be initialized by default, 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, Initialize is called
on the object.
Discussion:
Example:
type T1 is new Controlled with
record
... -- some components might have defaults
end record;
type T2 is new Controlled with
record
X : T1; -- no default
Y : T1 := ...; -- default
end record;
A : T2;
B : T2 := ...;
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.
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.
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.
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.
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.
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:
The value of the target becomes the assigned value.
The value
of the target is
adjusted.
Ramification: If any parts of the object
are controlled, abort is deferred during the assignment operation.
{
AI05-0067-1}
To adjust the value of a composite
object, the values of the components of the object are first adjusted
in an arbitrary order, and then, if the object is nonlimited 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.]
Ramification: {
AI05-0067-1}
Adjustment is never actually performed for values of an immutably limited
type, since all assignment operations for such types are required to
be built-in-place. Even so, we still define adjustment for all types
in order that the canonical semantics is well-defined.
Reason: {
AI05-0005-1}
The verbiage in the Initialize rule about access discriminants constrained
by per-object expressions is not necessary here, since such types are
either limited or do not have defaults, so the discriminant can only
be changed by an assignment to an outer object. Such an assignment could
happen only before any adjustments or (if part of an outer Adjust) only
after any inner (component) adjustments have completed.
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”.]
Reason: An
alternative design for user-defined assignment might involve an Assign
operation instead of Adjust:
procedure Assign(Target : in out Controlled; Source : in out Controlled);
Or perhaps even
a syntax like this:
procedure ":="(Target : in out Controlled; Source : in out Controlled);
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:
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;
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.
Forbidding such cases is not an option —
it would cause generic contract model violations.
{
AI05-0067-1}
When a function call or
aggregate
is used to initialize an object, the result of the function call or
aggregate
is an anonymous object, which is assigned into the newly-created object.
For such an assignment, the anonymous object might be
built in place,
in which case the assignment does not involve any copying. Under certain
circumstances, the anonymous object is required to be built in place.
In particular:
Discussion: {
AI05-0067-1}
We say assignment to built-in-place objects does not involve copying,
which matches the intended implementation (see below). Of course, the
implementation can do any copying it likes, if it can make such copying
semantically invisible (by patching up access values to point to the
copy, and so forth).
If the full type of any part of the object is immutably
limited, the anonymous object is built in place.
Reason: {
AI05-0067-1}
We talk about the full types being immutably limited, as this is independent
of the view of a type (in the same way that it is for determining the
technique of parameter passing). That is, privacy is ignored for this
purpose.
{
AI05-0005-1}
{
AI05-0067-1}
For function calls, we only require building in place for immutably limited
types. These are the types that would have been return-by-reference types
in Ada 95. We limited the requirement because we want to minimize disruption
to Ada 95 implementations and users.
To be honest: {
AI05-0232-1}
This is a dynamic property and is determined by the specific type of
the parts of the actual object. In particular, if a part has a class-wide
type, the tag of the object might need to be examined in order to determine
if build-in-place is required. However, we expect that most Ada implementations
will determine this property at compile-time using some assume-the-worst
algorithm in order to chose the appropriate method to implement a given
call or aggregate. In addition, there is no attribute or other method
for a program to determine if a particular object has this property (or
not), so there is no value to a more careful description of this rule.
In the case of an
aggregate,
if the full type of any part of the newly-created object is controlled,
the anonymous object is built in place.
Reason: {
AI05-0067-1}
This is necessary to prevent elaboration problems with deferred constants
of controlled types. Consider:
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;
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).
Ramification: An
aggregate
with a controlled part 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.
Similarly, an
aggregate
that has a controlled part but is not itself controlled and that is used
to initialize an object also has to be built in place. This is also a
change from Ada 95, but it is not an inconsistency as it only serves
to restrict implementation choices. This avoids problems if a type like
Dyn_String (in the example above) is used as a component in a type used
as a deferred constant in package P.
In other cases, it is unspecified whether the anonymous
object is built in place.
Reason: This is left unspecified so the
implementation can use any appropriate criteria for determining when
to build in place. That includes making the decision on a call-by-call
basis. Reasonable programs will not care what decision is made here anyway.
{
AI05-0067-1}
Notwithstanding
what this International Standard
says elsewhere, if an object is built in place:
Upon successful completion of the return statement
or
aggregate,
the anonymous object
mutates into the newly-created object; that
is, the anonymous object ceases to exist, and the newly-created object
appears in its place.
Finalization is not performed on the anonymous
object.
Adjustment is not performed on the newly-created
object.
All access values that designate parts of the anonymous
object now designate the corresponding parts of the newly-created object.
All renamings of parts of the anonymous object
now denote views of the corresponding parts of the newly-created object.
Coextensions of the anonymous object become coextensions
of the newly-created object.
To be honest: This “mutating”
does not necessarily happen atomically with respect to abort and other
tasks. For example, if a function call is used as the parent part of
an
extension_aggregate,
then the tag of the anonymous object (the function result) will be different
from the tag of the newly-created object (the parent part of the
extension_aggregate).
In implementation terms, this involves modifying the tag field. If the
current task is aborted during this modification, the object might become
abnormal. Likewise, if some other task accesses the tag field during
this modification, it constitutes improper use of shared variables, and
is erroneous.
Implementation Note: The intended implementation
is that the anonymous object is allocated at the same address as the
newly-created object. Thus, no run-time action is required to cause all
the access values and renamings to point to the right place. They just
point to the newly-created object, which is what the return object has
magically “mutated into”.
There is no requirement that 'Address of the
return object is equal to 'Address of the newly-created object, but that
will be true in the intended implementation.
For a function call, if the size of the newly-created
object is known at the call site, the object is allocated there, and
the address is implicitly passed to the function; the return object is
created at that address. Otherwise, a storage pool is implicitly passed
to the function; the size is determined at the point of the return statement,
and passed to the Allocate procedure. The address returned by the storage
pool is returned from the function, and the newly-created object uses
that same address. If the return statement is left without returning
(via an exception or a goto, for example), then Deallocate is called.
The storage pool might be a dummy pool that represents “allocate
on the stack”.
The Tag of the newly-created object may be different
from that of the result object. Likewise, the master and accessibility
level may be different.
An alternative implementation model might allow
objects to move around to different addresses. In this case, access values
and renamings would need to be modified at run time. It seems that this
model requires the full power of tracing garbage collection.
Implementation Permissions
Ramification: {
AI05-0067-1}
The relaxations apply only to nonlimited types, as
assignment_statements
are not allowed for limited types. This is important so that the programmer
can count on a stricter semantics for limited controlled types.
{
AI05-0067-1}
If an object is assigned the value of that same object, the implementation
need not do anything.
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.
{
AI05-0067-1}
For assignment of 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.
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.
Ramification: {
AI05-0005-1}
This potentially breaks a single assignment operation into many, and
thus abort deferral (see
9.8) needs to last
only across an individual component assignment when the component has
a controlled part. It is only important that the copy step is not separated
(by an abort) from the adjust step, so aborts between component assignments
is not harmful.
{
AI95-00147-01}
{
AI05-0067-1}
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).
Ramification: {
AI05-0005-1}
If the anonymous object is eliminated by this permission, there is no
anonymous object to be finalized and thus the Finalize call on it is
eliminated.
{
AI95-00147-01}
{
AI05-0005-1}
Note that if the anonymous object is eliminated but the new value is
not built in place in the target object, that Adjust must be called 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.
{
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:
any omitted Initialize call is not a call on a
user-defined Initialize procedure, and
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.
Reason: We don't want to eliminate objects
for which the Initialize might have side effects (such as locking a resource).
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
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
the assignment operations associated with omitted
Adjust calls are also omitted.
This permission applies to Adjust and Finalize calls
even if the implicit calls have additional external effects.
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
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
{
AI95-00161-01}
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
{
8652/0021}
{
AI95-00182-01}
Corrigendum: Added wording to clarify that the default initialization
(whatever it is) of an ancestor part is used.
{
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.
{
AI95-00147-01}
Additional optimizations are allowed for nonlimited controlled types.
These allow traditional dead variable elimination to be applied to such
types.
{
AI95-00318-02}
Corrected the build-in-place requirement for controlled
aggregates
to be consistent with the requirements for limited types.
{
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).
{
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.
Extensions to Ada 2005
{
AI05-0212-1}
Package Ada.Finalization now has Pure categorization,
so it can be mentioned for any package. Note that this does not change
the preelaborability of objects descended from Controlled and Limited_Controlled.
Wording Changes from Ada 2005
{
AI05-0013-1}
Correction: Eliminated coextensions from the “needs finalization”
rules, as this cannot be determined in general in the compilation unit
that declares the type. (The designated type of the coextension may have
been imported as a limited view.) Uses of “needs finalization”
need to ensure that coextensions are handled by other means (such as
in No_Nested_Finalization – see
D.7)
or that coextensions cannot happen.
{
AI05-0013-1}
Correction: Corrected the “needs finalization” rules
to include class-wide types, as a future extension can include a part
that needs finalization.
{
AI05-0026-1}
Correction: Corrected the “needs finalization” rules
to clearly say that they ignore privacy.
{
AI05-0067-1}
Correction: Changed “built in place” to Dynamic Semantics
and centralized the rules here. This eliminates the fiction that built
in place is just a combination of a permission and a requirement; it
clearly has noticeable semantic effects. This wording change is not intended
to change the semantics of any correct Ada program.
Ada 2005 and 2012 Editions sponsored in part by Ada-Europe