Previous Table of Contents Next


10.4.3.9. Controlled Types and Finalization

I hinted previously that Ada provides a mechanism that facilitates writing an application-specific garbage-collection scheme. Three activities must be supported:

  Initialization of an object just after its elaboration
  Finalization of the object just before its destruction
  User-defined assignment

This support is embodied in the standard library package Ada.Finalization.

    1 package Ada.Finalization is
    2
    3   type Controlled is abstract tagged private;
    4   procedure Initialize(Object : in out Controlled);
    5   procedure Adjust    (Object : in out Controlled);
    6   procedure Finalize  (Object : in out Controlled);
    7
    8   type Limited_Controlled is
    9     abstract tagged limited private;
   10   procedure Initialize
   11     (Object : in out Limited_Controlled);
   12   procedure Finalize
   13     (Object : in out Limited_Controlled);
   14 private
   15   ... -- not specified by the language
   16 end Ada.Finalization;

The type Ada.Finalization.Controlled is

  abstract—it is a root type; clients cannot declare objects.
  tagged—it is extensible; its operations are inherited by types derived from it.
  private—its internal structure cannot be directly accessed by a client.

The operations Initialize, Adjust, and Finalize can be overridden for a derived type. They are not abstract operations, however, so they need not be overridden. In this case, the root operations are essentially “no-ops” that have no discernible effect.

How do these operations work? Suppose you have

   type MyType is new Ada.Finalization.Controlled
     with some_extension

   declare
     Object: MyType;
   begin
     Object := some_expression;
     ...
   end;

When control passes into the declare block, the declaration of Object is elaborated. Because MyType is derived from Controlled, its (inherited or overriding) Initialize operation is now automatically called.

Now control passes to the assignment statement. Three actions occur:

1.  Finalize(Object) is automatically called to “clean up” Object before copying a new value into it.
2.  The expression is evaluated and the result copied into Object, as usual in an assignment.
3.  Adjust(Object) is automatically called.

Finally, when control passes out of the block, Object goes out of scope and is destroyed (typically, popped off the system stack). Before this happens, Finalize(Object) is automatically called.

Suppose MyType represents a linked list. Deriving it from Controlled allows me to develop my own garbage collection in terms of overriding operations.

  Finalize walks through the linked list, returning nodes one-by-one to the storage pool. This means that whenever a list variable goes out of scope, the entire list is reclaimed.
  Adjust provides a “deep copy.” Given L1 and L2, the assignment L1:=L2 first deallocates all the nodes of L1 (because Finalize(L1) is called automatically). The assignment itself does a “shallow copy” operation, that is, it just copies the header block of L2 to L1, but the overriding Adjust procedure copies its elements into newly allocated and linked nodes.

Ada.Finalization provides a clean and easy-to-understand equivalent of the constructor and destructor operations of other languages and moreover provides user-defined assignment in a fashion that is well-integrated with the other operations.

It is now easy to understand the rest of the interface of HB.Lists_Generic. The type List is derived from Ada.Finalization.Controlled. I declare three overriding operations; putting them in the private part ensures that a client cannot call them directly.


Previous Table of Contents Next