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