TEAM-ADA Archives

Team Ada: Ada Programming Language Advocacy

TEAM-ADA@LISTSERV.ACM.ORG

Options: Use Classic View

Use Proportional Font
Show Text Part by Default
Show All Mail Headers

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

Print Reply
Tucker Taft <[log in to unmask]>
Sun, 10 May 1998 09:07:14 -0400
text/plain (260 lines)
> Tucker Taft wrote:
> >
> > If you want to prevent the possibility of forgetting initial
> > value assignment, you could specify unknown discriminants
> > in the private type declaration.  E.g.:
> >
> >     type Object_Type(<>) is private;
> >
> > The full type need not have any discriminants at all, but by
> > doing the private declaration this way, a user of the type outside
> > the package is required to initialize all object declarations,
> > because simply "X : Object_Type;" is illegal (Object_Type is an
> > "indefinite" (view of a) type).
>
> This is a good suggestion, but it only gets you about
> half way there.  I want to be able to do the opposite
> as well: guarantee that *every* object creation
> causes one of my procedures to be called.

If this is a private type, then only places that have direct
access to the full type definition could violate this rule.

> Consider this example:
>
> ------------------------
> 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 Finalize (Obj : in out Finis);
>     Null_Finis : constant Finis := (Controlled with null record);
> end Final;
>
> --------------------------
> package body Final is
>     Ref_Count : Integer := 0;
>
>     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
>         Ref_Count := Ref_Count + 1;
>         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 := 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 Finish is:
>
> Count is: 0
> Count is: 0
> Count is:-1
>
> My understading is that we get this result because the
> object resulting from the Create call is finalized without
> having been initialized.

You seem to have a single global reference count.  What is the
purpose of such a reference count?  Normally a reference count
is associated with a potentially shared part of an object.  For
every new reference to the shared part, the count is bumped up.
When a reference goes away, the count is bumped down.
If the count goes to zero, you can reclaim the storage of the shared
part.

This is easily done in Ada 95 by having a potentially shared
part of an object include a reference count, initialized to one,
bumped up by Adjust, and decremented by Finalize.  For example:

    type Counted is private;

   ...
  private
    type Shared_Part is limited record
        Count : Natural := 1;
       ... -- other data in shared part
    end record;

    type Shared_Part_Ptr is access Shared_Part;

    type Counted is new Ada.Finalization.Controlled with record
        Ptr : Shared_Part_Ptr := new Shared_Part;
       ... -- perhaps other unsharable data
    end record;

    procedure Adjust(C : in out Counted);
    procedure Finalize(C : in out Counted);
 ...
    procedure Adjust(C : in out Counted) is
    begin
        if C.Ptr /= null then
            C.Ptr.Count := C.Ptr.Count + 1;
        end if;
    end Adjust;

    procedure Free is new Ada.Unchecked_Deallocation(
      Shared_Part, Shared_Part_Ptr);

    procedure Finalize(C : in out Counted) is
    begin
        if C.Ptr /= null then
            C.Ptr.Count := C.Ptr.Count - 1;
            if C.Ptr.Count = 0 then
                Free(C.Ptr);
            end if;
        end if;
    end Finalize;
  ...

This is the general framework.  You can add data into the sharable
part and the unsharable part.  If you wanted the sharable part
to be fully initialized as part of object construction, then rather
than making the "counted" type simply "private" you would make
it "(<>) private", and then the user of type "Counted" would need
to call a function with visibility to the full definition of Counted
to create an instance.

> ... So, in Ada 95, I can't figure out
> a way to do reference counting for every object created
> of my type.  (In C++, this would be done with a private
> "static" data member in the class.)

How could a static data member be used to do reference counting?
Normally reference counting needs a count in every potentially
sharable entity.

> The following is not enough:
>
> -------------------------------
> with Ada.Finalization; use Ada.Finalization;
> package Final is
>
>     type Finis is new Controlled with null record;  -- or with private

Never use "with null record" or "with record ..." when declaring
a controlled type.  Always use "with private" or simply "private"

>     -- Construct is a procedure, not a function, so that
>     -- it cannot be called for object initialization

Why not provide a function?

>     procedure Construct
>          (Obj : in out Finis;
>           A : type1; B : type2 ... );   -- construction parameters
>     procedure Initialize (Obj : in out Finis);
>     procedure Finalize (Obj : in out Finis);
>
> end Final;
> --------------------------------
>
> (where the body of Initialize increments the ref_count,
> the body of Finalize decrements ref_count), because it
> is not possible to keep the user from explicitly
> initializing objects of type Finis like this:
>
>     F : Final.Finis := (Controlled with null record);

You never want to make a controlled type visible (or a visible
extension), or else you lose control.  Hence, even if you

> This bypasses the call to Initialize.  (I know I
> can guarantee that Initialize will be called if Finis
> is derived from Limited_Controlled, but this misses
> the point because then I would not be able to use
> functions as contructors, and would be stuck with
> discriminant values if I wanted parameters to be
> supplied at object declaration.)

The simple solution is to declare "Finis" as "with private"
or simply "private."  That eliminates the ability to use
aggregates.  You are certainly right that if the clients of
your abstraction can use aggregates, then you have no control
whatsoever.

> What would solve this problem (and a number of others),
> I think, is added support in Ada.Finalization for
> user-defined activity at the time of object creation.

This language change might solve the problem you are having,
but the language already provides a solution, namely "with private"
or "private."  Have you tried using those as part of your solution?
For what it is worth, I normally recommend simply "private."  There
is no particular reason to "broadcast" that a particular type is
derived from Controlled.

> This could take the form:
>
>     procedure Create (Object : in out Controlled);    ....
>     procedure Create (Object : in out Limited_Controlled);
>
> in the spec of Ada.Finalization.
>
> The semantics are: Create is called at object creation
> time, just before initialization.
>
> Symmetrically, we could add:
>
>     procedure Destroy (Object : in out Controlled);   ....
>     procedure Destroy (Object : in out Limited_Controlled);
>
> Which have the semantics: Destroy is called when an
> object is destroyed, just after the last Finalize
> has been called for it before it goes out of
> existence.
>
> Adding a separate Destroy procedure would help
> clean up the current semantics of Finalize because
> a Finalize procedure actually does double duty in an
> Ada 95 program -- it handles both value-destruction
> and object-destruction.

I don't see the problem you are trying to solve.  You have
to make a controlled type private if you want to retain control.

> 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.

Again, I don't see the need, presuming you make the type
private.  Perhaps an example of a remaining problem when
the type is private would help me understand your problem.

> Is there anything about this (admittedly rough) proposal
> which violates "Ada theory" (as I like to call it)?

I still don't see the real problem you are trying to solve.

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

-Tucker Taft  [log in to unmask]

ATOM RSS1 RSS2