comp.lang.ada
 help / color / mirror / Atom feed
From: Felix Krause <contact@flyx.org>
Subject: Smart Pointers and Tagged Type Hierarchies
Date: Mon, 24 Jul 2017 17:41:37 +0200
Date: 2017-07-24T17:41:37+02:00	[thread overview]
Message-ID: <2017072417413775878-contact@flyx.org> (raw)

With Ada's controlled types, it is possible to implement smart pointers 
which manage heap objects with reference-counting. There is more than 
one tutorial showing how that works.

A problem I encounter is how this can be used with type hierarchies 
i.e. I have a smart pointer managing a tagged type, and I want to be 
able to derive from that tagged type and still be able to use my smart 
pointer with that new type. Let me give an example: Assume I want to 
implement an abstract type Stream that represents a stream of events 
(has nothing to do with Ada.Streams). I will use a slightly modified 
Rosen '95 name scheme here for clarity: Reference is the smart pointer, 
Instance is the actual object. Let this be the base type:

    package Stream is
       type Reference is new Ada.Finalization.Controlled with private;

       type Instance is abstract tagged limited private;
       type Instance_Pointer is access all Instance'Class;

       --  reference-counting implementation here
       overriding procedure Adjust (Object : in out Reference);
       overriding procedure Finalize (Object : in out Reference);

       --  fetches an event from the stream
       procedure Fetch (Object : in out Instance; Ret : out Event) is abstract;

       --  initialize the smart pointer with an object.
       --  the smart pointer takes control of that object and will 
deallocate it
       --  when reference count reaches zero.
       procedure Init (Object : in out Reference'Class; Impl : 
Instance_Pointer);

       function Implementation_Access (Object : Reference'Class) return 
Instance_Pointer;

       --  is called before deleting the instance. override if you have 
cleanup to do.
       procedure Finalize (Object : in out Instance) is null;
    private
       type Reference is new Ada.Finalization.Controlled with record
          Impl : Instance_Pointer;
       end record;

       type Instance is abstract tagged limited record
          Refcount : Natural := 1;
       end record;
    end Stream;

An example non-abstract type derived from this would be stream that 
reads events from a file:

    package File_Stream is
       type Reference is new Stream.Reference with null record;

       procedure Init (Object : in out Reference; Path : String);

       --  fetches the current position within the file
       procedure Current_Position (Object : in out Reference; Line, 
Column : out Positive);
    private
       type Instance is new Instance with record
          File : Ada.Text_IO.File_Access;
          --  possibly other fields, e.g. information needed for 
Current_Position
       end record;

       --  closes the file
       overriding procedure Finalize (Object : in out Instance);
    end File_Stream;

Some observations:

 * Unless all implementations are child classes of Stream, it is 
necessary to make the Instance type public.
 * A derived type, if it wants to provide additional operations (like 
Current_Position), must not only derive from Instance, but also from 
Reference, to be able to provide an type-safe interface to those 
operations.
 * As types derived from Stream possibly need to derive 
Stream.Reference, a consumer of a Stream object needs to take a 
Stream.Reference'Class as input. This type cannot be used for a record 
field, so I need to allocate it in heap memory and store a pointer if I 
want to memorize a Stream.Reference value anywhere.
 * The implementation of Current_Position is cumbersome as I need the 
Implementation_Access function and convert the result to 
File_Stream.Instance, which creates a needless downcast check.

I think this is not an ideal interface for the user and I am searching 
for a better alternative. One thing I thought of is having a generic 
pointer, so that only the Instance is tagged:

    package Stream is
       type Instance is abstract limited tagged private;

       --  fetches an event from the stream
       procedure Fetch (Object : in out Instance; Ret : out Event) is abstract;
    private
       type Instance is abstract tagged limited record
          Refcount : Natural := 1;
       end record;
    end Stream;

    generic
       type Implementation is new Stream.Instance with private;
    package Stream.Smart is
       type Reference is new Ada.Finalization.Controlled with private;

       --  reference-counting implementation
       overriding procedure Adjust (Object : in out Reference);
       overriding procedure Finalize (Object : in out Reference);
    private
       type Implementation_Access is access all Implementation'Class;

       type Reference is new Ada.Finalization.Controlled with record
          Data : access Implementation_Access;
       end record;
    end Stream.Smart;

This looks good at first glance. But now, all consumers of a Stream 
must also be generic and take an instance of the Smart package as 
generic parameter (at least if I want them to take the smart pointer 
and not Instance_Pointer as parameter, which is kind of the point).

Now I am wondering what others think of these approaches. Are there 
alternatives? Which one would be better from a user perspective?

-- 
Regards,
Felix Krause


             reply	other threads:[~2017-07-24 15:41 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-07-24 15:41 Felix Krause [this message]
2017-07-24 19:53 ` Smart Pointers and Tagged Type Hierarchies Dmitry A. Kazakov
2017-07-27 19:30   ` Felix Krause
2017-07-27 20:42     ` Dmitry A. Kazakov
2017-07-24 21:24 ` Chris Moore
2017-07-27 19:38   ` Felix Krause
2017-08-01  4:07     ` Randy Brukardt
2017-07-26 17:53 ` Ivan Levashev
2017-07-28  9:21 ` AdaMagica
2017-07-30 19:45   ` briot.emmanuel
2017-08-01  3:43 ` Randy Brukardt
2017-08-01  7:36   ` Dmitry A. Kazakov
2017-08-01 22:41     ` Randy Brukardt
2017-08-02  6:28       ` Dmitry A. Kazakov
2017-08-02 19:26         ` Randy Brukardt
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox