From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00, T_FILL_THIS_FORM_SHORT autolearn=unavailable autolearn_force=no version=3.4.4 Path: eternal-september.org!reader01.eternal-september.org!reader02.eternal-september.org!news.eternal-september.org!news.eternal-september.org!feeder.eternal-september.org!border1.nntp.ams1.giganews.com!nntp.giganews.com!peer01.ams1!peer.ams1.xlned.com!news.xlned.com!peer01.am4!peer.am4.highwinds-media.com!peer01.fr7!futter-mich.highwinds-media.com!news.highwinds-media.com!fx32.am4.POSTED!not-for-mail From: Felix Krause Newsgroups: comp.lang.ada Message-ID: <2017072417413775878-contact@flyx.org> MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1; format=flowed Content-Transfer-Encoding: 8bit Subject: Smart Pointers and Tagged Type Hierarchies User-Agent: Unison/2.2 X-Complaints-To: abuse@eweka.nl NNTP-Posting-Date: Mon, 24 Jul 2017 15:41:38 UTC Organization: Eweka Internet Services Date: Mon, 24 Jul 2017 17:41:37 +0200 X-Received-Body-CRC: 2673461072 X-Received-Bytes: 5960 X-Original-Bytes: 5784 Xref: news.eternal-september.org comp.lang.ada:47505 Date: 2017-07-24T17:41:37+02:00 List-Id: 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