TEAM-ADA Archives

Team Ada: Ada Programming Language Advocacy

TEAM-ADA@LISTSERV.ACM.ORG

Options: Use Forum View

Use Proportional Font
Show Text Part by Default
Condense Mail Headers

Message: [<< First] [< Prev] [Next >] [Last >>]
Topic: [<< First] [< Prev] [Next >] [Last >>]
Author: [<< First] [< Prev] [Next >] [Last >>]

Print Reply
Sender:
"Team Ada: Ada Advocacy Issues (83 & 95)" <[log in to unmask]>
Subject:
From:
Tucker Taft <[log in to unmask]>
Date:
Mon, 11 May 1998 18:46:28 -0400
X-To:
Reply-To:
Tucker Taft <[log in to unmask]>
Parts/Attachments:
text/plain (295 lines)
> [log in to unmask] wrote:
> >
> > (Ignoring the stuff about a temp to allow X := X; to work.)

You don't need to worry about "X := X" in Ada 95.  The compiler
is required to suppress the calls on Finalize/Adjust if the left hand side
and the right hand side denote the same object (or introduce a temp,
which is generally much less efficient -- see 7.6(19)).

On the other hand, normally controlled objects contain a pointer to
something in the heap, and it is still possible that you might assign
a controlled object from another object that points to the same
heap object.  Presumably that is what reference counts are
supposed to handle smoothly.

Stanley Allen wrote:

> This example gets more confusing to me the
> more I look at it.  For convenience, let me
> repeat the spec:
>
> -------------------------------
> with Ada.Finalization; use Ada.Finalization;
> package Final is
>
>     type Finis is private;
>     function Get_Count return Integer;
>     function Create return Finis;
>
> private
>
>     type Finis is new Controlled with null record;
>
>     procedure Initialize (Obj : in out Finis);
>     procedure Adjust (Obj : in out Finis);
>     procedure Finalize (Obj : in out Finis);
>
>     Null_Finis : constant Finis := (Controlled with null record);
>
> end Final;
> --------------------------------
>
> At the point of declaration of Null_Finis,
> the specs of the Init/Adj/Fin procedures for
> type Finis have been declared, but the bodies
> have not.  (BTW, the compilers complain that
> the declaration of the constant Null_Finis
> above these spec declarations is illegal because
> Null_Finis 'freezes' the type.)  This declaration
> should cause Adjust to be called for Null_Finis;
> ... [eliding general discussion of what might happen]

For an object initialized by an aggregate, Adjust is *not* called.

This is now a required "optimization," per Ada 95 AI-00083:

    !standard 07.06    (21)                    96-06-05  AI95-00083/02
    !class binding interpretation 95-07-27
    !status WG9 approved 95-06-14
    !status work item (letter ballot was 10-1-0) 96-06-05
    !status ARG approved (subject to letter ballot) 7-0-3  95-11-01
    !status received 95-07-27
    !subject Aggregates of a controlled type

    !summary 95-07-27

    When an object of a controlled type is created and explicitly
    initialized with an aggregate, the aggregate is built "in place";
    Finalize and Adjust are not called on the object as a whole.

    !question 95-07-27

    If an object of a controlled type is declared in the same package as
    the type, and initialized with an aggregate, is Program_Error raised?
    (No.)

    !recommendation 95-07-27

    When an object of a controlled type is created and explicitly
    initialized with an aggregate, the implementation must not create a
    separate anonymous object for the aggregate; it must create the value of
    the aggregate directly in the newly-created object.  Thus, there is no
    assignment from the anonymous object to the newly-created object, so the
    Finalize and Adjust that would be done for that assignment are not done.

  ...

> Unfortunately, I cannot determine what the effect
> is of declaring such constants because when I feed
> the following code to my two compilers at home (GNAT
> and Aonix), the results diverge:

The Aonix compiler is doing the right thing.  The problem is
that your type "Y" is not a controlled type, since its
parent type is not visibly controlled anywhere within the immediate
scope of Y.

> ----------------------------------------
> with Ada.Finalization; use Ada.Finalization;
> package Xxx is
>     type X is tagged private;
> private
>
>     type X is new Controlled with record
>         A : Integer;
>         B : Float;
>     end record;
>
>     procedure Initialize (Obj : in out X);
>     procedure Adjust (Obj : in out X);
>     procedure Finalize (Obj : in out X);
>
> end Xxx;
> ------------------------------------------
> with Text_Io; use Text_Io;
> package body Xxx is
>
>     procedure Initialize (Obj : in out X) is begin
>         Put_Line ("Initialize called for object of type X"); end;
>     procedure Adjust (Obj : in out X) is begin
>         Put_Line ("Adjust called for object of type X"); end;
>     procedure Finalize (Obj : in out X) is begin
>         Put_Line ("Finalize called for object of type X"); end;
>
> end Xxx;
> -------------------------------------------
> with Xxx; use Xxx;
> package Yyy is
>     type Y is tagged private;
>     Null_Y : constant Y;        -- common, innocuous
> private
>
>     type Y is new X with null record;

It turns out that Y is *not* controlled, even though you might
think it is.  X is not visibly derived from Controlled, so Y
does not inherit, and cannot override, the primitive operations
Initialize/Adjust/Finalize.  See RM95 7.3.1(6).

>     procedure Initialize (Obj : in out Y);
>     procedure Adjust (Obj : in out Y);
>     procedure Finalize (Obj : in out Y);

These declarations don't override anything.  They are declaring
procedures that just happen to be called "Initialize"/"Adjust"/"Finalize."
>
>     -- the big question: what's happening here?
>     Null_Y : constant Y := (X with null record);

Even *if* Y had been controlled, neither Y's Adjust nor Y's Finalize
would be called, per AI-00083.

In any case, X's (default) Initialize should be called, since
the extension aggregate you used has an "X" part which needs to be
default initialized.  The RM allows the compiler to build the
initial value for Null_Y "in place," so there is no need
to copy and Adjust the aggregate, so all you should get is
a single call on X's Initalize, passed the view conversion "X(Null_Y)."

> end Yyy;
> --------------------------------------------
> with Text_Io; use Text_Io;
> package body Yyy is
>
>     procedure Initialize (Obj : in out Y) is begin
>         Put_Line ("Initialize called for object of type Y"); end;
>     procedure Adjust (Obj : in out Y) is begin
>         Put_Line ("Adjust called for object of type Y"); end;
>     procedure Finalize (Obj : in out Y) is begin
>         Put_Line ("Finalize called for object of type Y"); end;
>
> end Yyy;
> ---------------------------------------------
> with Yyy; use Yyy;
> with Text_Io; use Text_Io;
> procedure Usey is            -- main program
>     Y1 : Y;

This is just a record containing a controlled part (the "X" part).
That part needs to be default initialized, by calling X's Initialize.

> begin
>     Y1 := Null_Y;

This involves an assignment where the "X" part of Y is controlled.
Hence, the X part of Y1 needs to be Finalized, then Null_Y is
copied onto Y1, and then the X part of Y1 needs to be Adjusted.

At scope exit, the X part of Y1 needs to be finalized.

> end Usey;

At program exit, the X part of Null_Y needs to be finalized.

> ----------------------------------------------
>
> Aonix (Win95 7.1) says this when I run Usey:
>
> Initialize called for object of type X
> Initialize called for object of type X
> Finalize called for object of type X
> Adjust called for object of type X
> Finalize called for object of type X
> Finalize called for object of type X
>
> which can't be right.

Ah, but it is, because your "Y" type is not controlled,
but it contains a controlled X part.

> GNAT (Win95 3.10p) says this:
>
> Initialize called for object of type Y
> Finalize called for object of type Y
> Adjust called for object of type Y
> Finalize called for object of type Y
> Finalize called for object of type Y
>
> Wierd, huh?  For GNAT, there are no objects
> of type X; for Aonix, no objects of type Y.
> In GNAT's scheme, there is an "extra" Finalize
> which matches no Initialize or Adjust.  For
> Aonix, they match up, but it's obviously bogus.

Actually the Aonix results are not bogus.

If Y had in fact been controlled, either by being declared in
a child of Xxx where it could "see" that X was controlled, or
by making Xxx.X visibly controlled, e.g.:

    type X is new Controlled with private;

then the sequence you should see is:

  Initialize called for object of type X  -- def. init of X part of Null_Y
  Initialize called for object of type Y  -- default init of local variable Y1
  Finalize called for object of type Y    -- pre-assign fin. for Y1
  Adjust called for object of type Y      -- post-assign adj. for Y1
  Finalize called for object of type Y    -- end of scope for var Y1
  Finalize called for object of type Y    -- end of scope for Null_Y

So the GNAT results are almost right if Y had been controlled (which it isn't),
but the GNAT results are still missing the call on Initialize for the
X part of Null_Y.

Note that as good coding practice, the Initialize routine for Y should
call the Initialize routine for X, the Adjust for Y should call the
Adjust for X, and the Finalize for Y should call the Finalize for X.

Finally, I would generally recommend you dispense with Initialize
routines, unless you can't figure out a way to do the default
initialization required any other way.  At least on some implementations,
having a user written Initialize routine can make controlled types
significantly less efficient than they already are.

> ...
> It's not clear to me that RM 7.6(11) that the aggregate
> as a whole does not cause Init/Adj to be called;

AI-00083 requires that Init/Adj *not* be called for an object
initialized by an aggregate.  In any case, an aggregate by itself never
involves a call on Initialize or Adjust for the whole aggregate,
though it may involve calls on Initialize or Adjust for controlled
*parts* of the aggregate.

> ...
> Somebody's wrong somewhere.

The major problem is that Y is derived from a type (X)
that is not visibly controlled.  Hence, declaring
Initialize/Adjust/Finalize for Y has no special effect (per RM95 7.3.1(6)).

If you change the declaration of X to be

   type X is new Controlled with private;

at least you will begin to see something like what you would expect.

However, it is important to realize that Initialize/Adjust is
not called on aggregates as a whole, nor on objects initialized with
an aggregate.  The aggregate must have the value as you want
it to be seen by Finalize.  Typically, this means that the reference
count should be set to 1 in the heap object pointed to by a controlled object
created by an aggregate.  E.g.:

     My_Controlled'(ptr => new heap_obj(ref_count => 1, ...), ...)

And for sure the controlled type should be private or a private
extension, so only code inside the package can utilize aggregates.

> Stanley Allen
> mailto:[log in to unmask]

-Tucker Taft  [log in to unmask]

ATOM RSS1 RSS2