TEAM-ADA Archives

Team Ada: Ada Programming Language Advocacy

TEAM-ADA@LISTSERV.ACM.ORG

Options: Use Classic View

Use Monospaced Font
Show Text Part by Default
Condense Mail Headers

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

Print Reply
Sender: "Team Ada: Ada Advocacy Issues (83 & 95)" <[log in to unmask]>
Date: Sat, 9 May 1998 19:40:46 -0500
Reply-To: Stanley Allen <[log in to unmask]>
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset=us-ascii
From: Stanley Allen <[log in to unmask]>
Parts/Attachments: text/plain (279 lines)
[log in to unmask] wrote:
>
>   You need an Adjust that increments the reference count, and
> Create should not increment it.  A new object can come into
> existence either with an Initialize call or (as above) with an
> Adjust call, so both must be there and ready to increment the
> reference count (or whatever).  Other functions which happen
> to "return Final.Finis" (like Create in your example) should
> not be fiddling with the reference count because they are not
> creating or destroying objects - other than the creation or
> destruction that is already accounted for by compiler
> generated Initialize/Adjust/Finalize calls - they are merely
> accessing existing objects.

I stand corrected.  Below is the modified code
which takes this into account, in case anyone else
was as confused as me (or if I confused someone
besides myself!):

-------------------------------
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;
--------------------------------
package body Final is

    Ref_Count : Integer := 0;

    procedure Initialize (Obj : in out Finis) is
    begin
        Ref_Count := Ref_Count + 1;
    end Initialize;

    procedure Adjust (Obj : in out Finis) is
    begin
        Ref_Count := Ref_Count + 1;
    end Adjust;

    procedure Finalize (Obj : in out Finis) is
    begin
        Ref_Count := Ref_Count - 1;
    end Finalize;

    function Get_Count return Integer is
    begin
        return Ref_Count;
    end Get_Count;

    function Create return Finis is
    begin
        return Null_Finis;
    end Create;

end Final;
-------------------------------
with Final;
with Text_Io; use Text_Io;
with Ada.Finalization; use Ada.Finalization;
procedure Finish is

begin

    Put_Line ("Count is:" & Integer'Image (Final.Get_Count));

    declare
        X : Final.Finis;
    begin
        X := Final.Create;
        Put_Line ("Count is:" & Integer'Image (Final.Get_Count));
    end;

    Put_Line ("Count is:" & Integer'Image (Final.Get_Count));

    declare
        Y : Final.Finis := Final.Create;
    begin
        Put_Line ("Count is:" & Integer'Image (Final.Get_Count));
    end;

    Put_Line ("Count is:" & Integer'Image (Final.Get_Count));

end Finish;
------------------------

The output of this is what I expected/wanted:

Count is: 0
Count is: 1
Count is: 0
Count is: 1
Count is: 0

Hurrah!  Thank you, Mr. Moran.


> >Ada 95 Initialize/Adjust/Finalize are not as flexible
> >as C++ constructor/destructors because the have only
> >to do with the *contents* of an object during its life
> >*between* creation and deletion.  It would be nice to
> >extend the programmer's control to permit activity at
> >the endpoints of the object lifetime as well.
>   Initialize/Adjust/Finalize are called by the compiler
> exactly at the endpoints of existence.  I don't understand
> what you might mean by accessing the object before it is
> created or after it is deleted.
>

Is this accurate?  Finalize is called not only
at the point of object destruction but also at
the point of assignment, when the value is being
overwritten.  That's why I say that Init/Adj/Fin
confuse the issues of object creation/destruction
with the changing of object contents.

Because of this, there is a loose end, the situation
which may cause Finalize to be called twice --
the abort-during-finalize problem.

A Destroy procedure would take care of this, because
it would not be called as part of assignment, only
when an object is destroyed.

>   BTW, this has apparently been difficult for compiler
> writers, too.  If you are making test programs make sure you
> have a compiler that's doing the correct thing.  There have
> been problems in the past.

Unfortunately, I can't tell if the following
two examples reflect problems in the compilers
I am using or problems in my understanding of
Finalization.

Example #1, assume the spec of Final is as given
above.  If the body looks like this:

------------------------------
with Text_Io; use Text_Io;
with Ada.Integer_Text_Io; use Ada.Integer_Text_Io;
package body Final is

    Ref_Count : Integer := 0;

    procedure Initialize (Obj : in out Finis) is
    begin
        Ref_Count := Ref_Count + 1;
        Put (':'); Put (Ref_Count, Width => 0); New_Line;
    end Initialize;

    procedure Adjust (Obj : in out Finis) is
    begin
        Ref_Count := Ref_Count + 1;
        Put ('='); Put (Ref_Count, Width => 0); New_Line;
    end Adjust;

    procedure Finalize (Obj : in out Finis) is
    begin
        Ref_Count := Ref_Count - 1;
        Put ('.'); Put (Ref_Count, Width => 0); New_Line;
    end Finalize;

    function Get_Count return Integer is
    begin
        return Ref_Count;
    end Get_Count;

    function Create return Finis is
    begin
        return Null_Finis;
    end Create;

end Final;
--------------------------------

Here I print the Ref_Count after an indicator
in each of the three automatic routines.
Initialize's indicator is a colon (':'),
Adjust's is an equal sign ('='); Finalize's
is a period ('.').

Given this program:

-----------------------
with Final;
with Text_Io; use Text_Io;
procedure Finish2 is

with Final;
begin

    Put_Line ("Z");
    declare
        Z1 : Final.Finis := Final.Create;
        Z2 : Final.Finis;
    begin
        Z2 := Z1;
    end;

    Put_Line ("end");

end Finish2;
-------------------------

I see the following output from both GNAT and Aonix:

Z
=1
=2
.1
:2
.1
=2
.1
.0
end
.-1

Notice the negative one after the "end" output.
This means that Finalize is being called one
last time as the program ends.  I suppose this
is the finalization of the object Null_Finis.
But does this make sense?  Null_Finis was never
Initialized or Adjusted.  Now my ref counts are
messed up at the end.

The second example is similar; both GNAT and
Aonix give the same output.

Change the spec of Final to eliminate the
declaration of Null_Finis; change the body
of Final so that the Create procedure does
this instead of returning Null_Finis:

    return (Controlled with null record);

According to my reading of Cohen, this
return statement should actually cause two
temporary objects to be created, one by the
evaluation of the aggregate, the other by
the return statement.  Running Finish2 after
these changes gets me this:

Z
=1
.0
=1
.0
:1
.0
=1
.0
.-1
end

Still end up with that negative one!  And all
through this section of code, it never thinks
I have two objects: my ref count is one at most
the whole time.

Is Ada 95's finalization broken?  Are these compilers?
Or are my synapses?

Stanley Allen
mailto:[log in to unmask]

ATOM RSS1 RSS2