singletonadastatic-membersstatic-initialization

Ada initialization of controlled singleton variable


I am trying to create a package with a singleton variable that is auto initialized. If the singleton variable is not a controlled type, then the compiler doesn't complain, but when I make it controlled, I get the warning:"cannot call Initialize before body seen"

Followed by: "Program error will be raised at run time."

So, I think the compiler wants the singleton variable to be hidden from child objects, but this is not what I want. Here's my code:

with Ada.Finalization;

package static_member is
   type singleton_type is tagged limited private;

private
   type singleton_type is new Ada.Finalization.Limited_Controlled with record
      data_to_init: Natural;
   end record;

   overriding procedure Initialize(data: in out singleton_type);
   overriding procedure Finalize(data: in out singleton_type);

   --This is where I get the warnings.
   --I want singleton to be visible to child objects.
   singleton: singleton_type;
end static_member;

Then the package body is:

package body static_member is
   overriding procedure Initialize(data: in out singleton_type) is
   begin
      data.data_to_init := 0;
   end Initialize;

   overriding procedure Finalize(data: in out singleton_type) is null;
end static_member;

How would I create a singleton variable that is initialized, and ready to use by the time object instances begin to be created? Thanks in advance.


Solution

  • The answer to this question as posed is that - as the compiler said - "cannot call Initialize before body seen"; that is, the place in the program text where Initialize is called must be after the place in the program text where the body of Initialize is found. This is a (maybe historical) consequence of a desire for one-pass compilers.

    Because the body of Initialize has to appear in the body of the package (unless it's null, which is legal in Ada 2012), this means that singleton can only be declared in the package body (after the body of Initialize, of course).

    This means that you need to provide access subprograms for use by child packages: possibly

    with Ada.Finalization;
    package Static_Member with Elaborate_Body is
       type Singleton_Type is tagged limited private;
       function Singleton return access Singleton_Type;
    private
       type Singleton_Type is new Ada.Finalization.Limited_Controlled with record
          Data_To_Init: Natural;
       end record;
    
       overriding procedure Initialize (Data: in out Singleton_Type);
       overriding procedure Finalize (Data: in out Singleton_Type);
    end Static_Member;
    
    package body Static_Member is
       overriding procedure Initialize (Data: in out Singleton_Type) is
       begin
          Data.Data_To_Init := 0;
       end Initialize;
    
       overriding procedure Finalize (Data: in out Singleton_Type) is null;
    
       The_Singleton: aliased Singleton_Type;
    
       function Singleton return access Singleton_Type is (The_Singleton'Access);
    end Static_Member;
    

    The reason for the Elaborate_Body is that, without it, the compiler doesn't have to elaborate the package body before other, using, packages that call package subprograms (e.g. Singleton): so

    The_Singleton: aliased Singleton_Type;
    

    with the implicit call to Initialize might not have happened (which would lead to a Program_Error with a different cause, known in the trade as "access before elaboration" or ABE).


    That said, there are other approaches, which avoid the use of Ada.Finalization. Your sample code doesn't show any particular reason to use it, and indeed there's no point in overriding Finalize (unless you want something to happen at program shutdown?)

    One possibility (for a simple case) would be to declare Singleton_Type with initialization:

    type Singleton_Type is record
       Data_To_Init : Natural := 0;
    end record;
    

    An alternate would be to do the initialization in the package body:

    package Static_Member with Elaborate_Body is
       type Singleton_Type is limited private;
       function Singleton return access Singleton_Type;
    private
       type Singleton_Type is limited record
          Data_To_Init : Natural;
       end record;
    end Static_Member;
    
    package body Static_Member is
       The_Singleton: aliased Singleton_Type;
    
       function Singleton return access Singleton_Type is (The_Singleton'Access);
    begin
       The_Singleton.Data_To_Init := 0;
    end Static_Member;