TEAM-ADA Archives

Team Ada: Ada Programming Language Advocacy

TEAM-ADA@LISTSERV.ACM.ORG

Options: Use Forum View

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

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

Print Reply
Subject:
From:
Randy Brukardt <[log in to unmask]>
Reply To:
Randy Brukardt <[log in to unmask]>
Date:
Tue, 21 May 2002 14:38:48 -0500
Content-Type:
text/plain
Parts/Attachments:
text/plain (160 lines)
Maxim Reznik wrote:
> Randy Brukardt wrote:
> [skip]
>
> >
> > But the *really* best solution is an OOP-based solution. This is
extensible
> > and type-safe and doesn't require unsafe compares and doesn't even need
> > (visible) access types. Admittedly, there is a bit more work up front,
but I
> > think access types should avoided unless there is really dynamic
allocation
> > (which can't happen with subprograms in Ada). One of the really nice
things
> > about Ada 95 is that you can do useful OOP without using any access
types.
> >
> >               Randy Brukardt.
> >
>
> Could you explain please how to avoid visible access type?
> I tought we should write something like
>
> package Subscriber is
>
>     type Listener is abstract tagged null record;
>     procedure Action (L : Listener) is abstract;
>
>     type Listener_Ptr is access all Listener'Class;
>
>     procedure Register (P : Listener_Ptr);
>     procedure Unregister (P : Listener_Ptr);
>
> end Subscriber;

I would use something like:

with Ada.Finalization;
package Subscriber is

    type Listener is abstract tagged limited private;
    procedure Action (L : Listener) is abstract;

    procedure Register (L : in out Listener'Class);
    procedure Unregister (P : in out Listener'Class);

private

    type Listener_Ptr is access all Listener'Class;

    type Listener is abstract new Ada.Finalization.Limited_Controlled
       with record
         Next_Listener : Listener_Ptr;
    end record;

    procedure Finalize (L : in out Listener);
end Subscriber;

Then the body would be something like:

package body Subscriber is

    Subscriber_List : Listener_Ptr;

    procedure Register (L : in out Listener'Class) is
    begin
        -- Possibly check for already registered here.
        L.Next_Listener := Subscriber_List;
        Subscriber_List := L'Unchecked_Access;
    end Register;

    procedure Unregister (P : in out Listener'Class) is
        ... remove P'Unchecked_Access from the list. No deallocation
        ... is needed, that is the caller's responsibility.

    procedure Finalize (P : in out Listener) is
        ... remove P'Unchecked_Access from the list.

end Subscriber;

This is limited so that we know for sure that the registered and
unregistered objects are the same (thus we can compare the access values,
which *is* guaranteed for objects).

This is a controlled type so it isn't possible to have an Action pointing at
something that no longer exists. That has the nice property of allowing
action (objects) to be declared in nested scopes, and have them
automatically deregistered when they go out of scope.

Given that this has to be a controlled type to be safe, we could use
Initialize to register it, and Finalize to unregister it. That would look
something like:

with Ada.Finalization;
package Subscriber is

    type Listener is abstract tagged limited private;
         -- Declaring an object of a type derived from Listener registers
         -- it, letting it finalize deregisters it.
    procedure Action (L : in out Listener) is abstract;

private

    type Listener_Ptr is access all Listener'Class;

    type Listener is abstract new Ada.Finalization.Limited_Controlled
       with record
         Next_Listener : Listener_Ptr;
    end record;

    procedure Initialize (L : in out Listener);
    procedure Finalize (L : in out Listener);
end Subscriber;

Then you can use objects declared in blocks and subprograms to control the
lifetime. But that might be too confining in some systems.

To go back to the original question of this thread, the big advantage of the
OOP solution is the ability to have state data packaged along with the
callback. A smaller advantage is to eliminate the possibility of using
something that is no longer meaningful.

To see this, consider using this mechanism to do something on windows. You
need somewhere to hold onto the window object that you're going to act on.
This could look something like:

with Claw, Subscriber;
package Window_Actions is

    type Window_Action_Type is new Subscriber.Listener with record
        My_Window : Claw.Any_Window_Access_Type;
    end record;

    procedure Action (Item : in out Window_Action_Type);

end Window_Actions;

In this case, Action simply has to look at the data object in order to find
what to act on. No unsafe global variables needed.

And then this can used as a component in a window object, so we don't have
to worry about lifetime issues. (We could have made My_Window an access
discriminant if that was the primary intended use of this package.)

    type App_Window is new Claw.Frame_Window.Frame_Window_Type with record
        Action : Window_Action_Type;
    end record;

When initializing App_Window, we only have to set Action.My_Window:
    App : App_Window;

    App.Action.My_Window := App'Unchecked_Access;

(This could be done automatically if My_Window was an access discriminant.)

I hope it is obvious why the O-O solution should be preferred over the
unstructured, direct use of 'Access one.

           Randy Brukardt.

ATOM RSS1 RSS2