Tuesday, September 18, 2012

AdaTutor - More Records and Types (3)

Abstract Types and Abstract Subprograms

   package P is
      type Abstract_Date is abstract tagged null record;
      procedure Display(Ad : in Abstract_Date) is abstract;
   end P;

In this example for Ada 95 only, type Abstract_Date is an abstract type.  We can't declare objects of that type, but we can derive other types from it.  For example, we can derive type Date by adding three fields, and then derive type Complete_Date from type Date by adding one more field.

The abstract procedure does not have a body, only a specification.  However, it lets us write procedures built around types derived from the abstract type.  For example, we could write overloaded procedures Display for types Date and Complete_Date (these procedures will have bodies as well as specifications).  Also, by declaring an access type to type Abstract_Date, we could again make use of dynamic dispatching to our overloaded versions of Display:

   package P is
      type Abstract_Date is abstract tagged null record;
      procedure Display(Ad : in Abstract_Date) is abstract;
   end P;

   with P; use P;
   package Q is
      ...
      type Ptr is access Abstract_Date;
      type Date is new Abstract_Date
         with record ... -- Day, Month, Year
      type Complete_Date is new Date
         with record ... -- Day_Of_Week
      procedure Display(D : in Date);
      procedure Display(Cd : in Complete_Date);
   end Q;

The advantage of this is that we can write package P, with all its abstract procedures, before we write the code in package Q detailing what the derived types (Date and Complete_Date) look like.

Controlled Types

Ada 95 has Controlled Types, which cause the program automatically to call special procedures whenever an object of a controlled type is created, assigned, or destroyed.  The Ada 95 package Ada.Finalization contains abstract tagged private types Controlled and Limited_Controlled.  We'll discuss type Controlled first.  The package also contains procedures Initialize, Adjust, and Finalize, but they're null and do nothing.  We'll see shortly how we can override the null procedures with our own procedures when we need to.

We can create our own controlled types by extending the type Ada.Finalization.Controlled.  Whenever an object of a controlled type is created, the procedure Initialize is automatically called, with the object as the single in out parameter of Initialize. Whenever an object of a controlled type is destroyed, Finalize is automatically called.  When an object of a controlled type is stored into, first Finalize is automatically called for the object stored into, then the assignment is done, and then Adjust is automatically called for the object stored into.

Controlled types must be declared at the library level, so they're usually declared in a package specification.  In this example, we create a controlled type Special with a single field containing one Integer:

   with Ada.Finalization; use Ada.Finalization;
   package Demo_Controlled is
      type Special is new Controlled with record
         I : Integer;
      end record;
      procedure Initialize(Int : in out Special);
      procedure Adjust(Int : in out Special);
      procedure Finalize(Int : in out Special);
      procedure Set(Target : out Special; To : in Integer);
   end Demo_Controlled;

We don't have to write our own versions of all three procedures, Initialize, Adjust, and Finalize if we don't need them; for any we don't write, the null procedures in Ada.Finalization will be called.  Here we chose to write all three as an example.  We also wrote a procedure Set to set an object of type Special to an Integer.

In our example, Initialize, Adjust, and Finalize will merely print messages so we can see when they're called.  Here's the package body:

   with Ada.Text_IO; use Ada.Text_IO;
   package body Demo_Controlled is
      procedure Initialize(Int : in out Special) is
      begin
         Put_Line("Initialize was called.");
      end Initialize;
     
      procedure Adjust(Int : in out Special) is
      begin
         Put_Line("Adjust was called.");
      end Adjust;

      procedure Finalize(Int : in out Special) is
      begin
         Put_Line("Finalize was called.");
      end Finalize;

      procedure Set(Target : out Special; To : in Integer) is
      begin
         Target.I := To;
      end Set;
   end Demo_Controlled;

The main program below creates two objects of type Special, calls Set for one of them, and assigns one object of type Special to another.  Below the main program, we show the program output with some explanation:

   with Demo_Controlled, Ada.Text_IO;
   use Demo_Controlled, Ada.Text_IO;
   procedure Test is
      A, B : Special;
   begin
      Put_Line("I'm about to call Set.");
      Set(Target => A, To => 3);
      Put_Line("Set returned.");
      B := A;
   end Test;

Initialize was called.      \  A and B came into existence when the declarative
Initialize was called. /  region of the main program was elaborated.
I'm about to call Set. \  Set didn't create, assign, or destroy controlled
Set returned. /  objects, so nothing was called automatically.
Finalize was called. -  B was "finalized" before being overwritten.
Adjust was called. -  B was "adjusted" after receiving a new value.
Finalize was called. \  A and B went out of existence at
Finalize was called. /  the end of the main program.

The type Limited_Controlled in Ada.Finalization is the same as the type Controlled, except that it's an abstract tagged limited private type.  Because we can't assign objects of a limited type, only procedures Initialize and Finalize are available for that type.  There's no procedure Adjust for type Limited_Controlled.

< prev   next >

No comments: