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 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,924aad49bcf9e4e7,start X-Google-Attributes: gid103376,public From: Richard Irvine Subject: Cumbersome Polymorphism Date: 1997/01/23 Message-ID: <32E7ABE8.3BF3@eurocontrol.fr> X-Deja-AN: 211751579 content-type: text/plain; charset=us-ascii organization: EUROCONTROL Experimental Centre mime-version: 1.0 newsgroups: comp.lang.ada x-mailer: Mozilla 3.0C-EEC (X11; I; HP-UX A.09.05 9000/755) Date: 1997-01-23T00:00:00+00:00 List-Id: Here is a simple coding problem, some attempted and possible solutions, and finally some moderately contentious conclusions. I want to call a procedure of a package to store a value in one of that package's internal variables and then have another procedure which will do something with that internal variable at a later time. And in these OO times let's say that the value to be stored can be polymorphic, i.e. its type could be any of the types in a class of types. -------------------- Attempted Solution 1 -------------------- To start with define the tagged types: package Types is type Abstract_Type is abstract tagged null record; procedure Do_Something_To(A_Descendent : Abstract_Type); type Descendent1 is new Abstract_Type with null record; type Descendent2 is new Abstract_Type with null record; end; Here is a fairly natural first attempt at the package which will store the value: with Types; use Types; package A_Package is procedure Store(A_Descendent : Abstract_Type'Class); procedure Do_Something_To_The_Stored_Descendent; end; package body A_Package is The_Stored_Descendent : Abstract_Type'Class; procedure Store(A_Descendent : Abstract_Type'Class) is begin The_Stored_Descendent := A_Descendent; end; procedure Do_Something_To_The_Stored_Descendent is begin Do_Something_To(The_Stored_Descendent); end; end; The compiler points out why the body of A_Package is not correct with the message initialization required in class-wide declaration referring to The_Stored_Descendent : Abstract_Type'Class; A variable declared to be of type Type'Class must be initialised when it is declared. In other words, one cannot simply say that a variable may contain an instance of any of the types in a class. (I suppose that the underlying problem is that since the sizes of the types in the class are different, the compiler does not know how much storage to set aside for the variable). Furthermore, if I were to initialise the variable using an instance of a member of the class of types I could only use the variable subsequently to store other instances of that member of the class, in other words I could not use the variable polymorphically. So clearly this is the wrong way to go about doing things in Ada. The (a) right way is to make use of access types which may access instances of all of the types in the class, e.g. ---------- Solution 2 ---------- package Types is type Abstract_Type is abstract tagged null record; procedure Do_Something_To(A_Descendent : Abstract_Type); type Access_Abstract_Type_Class is access Abstract_Type'Class; type Descendent1 is new Abstract_Type with null record; type Descendent2 is new Abstract_Type with null record; end; package body A_Package is Access_The_Stored_Descendent : Access_Abstract_Type_Class; procedure Store(A_Descendent : Abstract_Type'Class) is begin Access_The_Stored_Descendent := new Abstract_Type'Class'(A_Descendent); end; procedure Do_Something_To_The_Stored_Descendent is begin Do_Something_To(Access_The_Stored_Descendent.all); end; end; This is a possible solution to the problem. (In this case the compiler knows how much storage to set aside for the access type, because this is independent of the size of the type to which it points). However, it seems that in order to make a very simple use of polymorphism, I have been obliged to introduce access types. And now by bringing in access types I have imported the problems that go with them. The above package does not free the storage which is allocated in the Store operation. In this case, I could simply introduce an explicit deallocation in the Store operation, before allocating new storage. However, as is well known, the problem with requiring the user of a type to perform explicit deallocation is that he may forget to do so somewhere in the code, resulting in a memory leak. Of course, Ada provides a mechanism for automating deallocation, using controlled types, so a better solution would make use of this: ---------- Solution 3 ---------- with Ada.Finalization; use Ada.Finalization; package Types is type Abstract_Type is abstract tagged private; procedure Do_Something_To(A_Descendent : Abstract_Type); type Access_Abstract_Type_Class is access Abstract_Type'Class; type Controlled_Access_Abstract_Type_Class is new Controlled with record The_Access_Abstract_Type_Class: Access_Abstract_Type_Class := null; end record; type Descendent1 is new Abstract_Type with private; type Descendent2 is new Abstract_Type with private; private type Abstract_Type is abstract tagged null record; procedure Finalize ( Object : in out Controlled_Access_Abstract_Type_Class); type Descendent1 is new Abstract_Type with null record; type Descendent2 is new Abstract_Type with null record; end; with Ada.Tags; use Ada.Tags; with Ada.Unchecked_Deallocation; with Text_IO; use Text_IO; package body Types is procedure Do_Something_To(A_Descendent : Abstract_Type) is begin -- write out the type (tag) of the descendent Text_IO.Put_Line("I am a " & Expanded_Name(A_Descendent'Tag)); end; procedure Free is new Ada.Unchecked_Deallocation (Object => Abstract_Type'Class, Name => Access_Abstract_Type_Class); procedure Finalize (Object : in out Controlled_Access_Abstract_Type_Class) is begin Put_Line("Finalize " & Expanded_Name(Object'Tag)); Free(Object.The_Access_Abstract_Type_Class); end; end; with Ada.Finalization; use Ada.Finalization; package body A_Package is Controlled_Access_The_Stored_Descendent : Controlled_Access_Abstract_Type_Class; procedure Store(A_Descendent : Abstract_Type'Class) is begin Controlled_Access_The_Stored_Descendent := Controlled_Access_Abstract_Type_Class' (Controlled with The_Access_Abstract_Type_Class => new Abstract_Type'Class'(A_Descendent)); end; procedure Do_Something_To_The_Stored_Descendent is begin Do_Something_To(Controlled_Access_The_Stored_Descendent .The_Access_Abstract_Type_Class .all); end; end; ----------- Conclusions ----------- First of all, I look forward to improvements or corrections that anyone would like to suggest. Probably solution 3 is not the best or most straightforward possible, but consider the effort involved in constructing this solution, compared with the simplicity of the problem. Before using Ada I used Smalltalk. A Smalltalk variable can hold an instance of any class. Of course, this is possible because a Smalltalk variable is just a pointer to dynamically allocated storage. The important point though is that the pointers and the problems of allocation and deallocation are hidden from the Smalltalk programmer, while the Ada programmer is obliged to be (painfully) aware of them much of the time. (I know next to nothing about Java but believe that it has similar benefits, packages too). I am left with the feeling that for object oriented progamming Ada 95 is heavily cerebral, not to say cumbersome. There are certainly many large, highly reliable or safety-critical applications where it should be the language of choice. And at the other end of the spectrum Smalltalk is probably preferable for small rapid prototyping. For the enormous variety of applications in between I think that the choice between Smalltalk, Java, Ada etc. still requires careful consideration, despite the advent of Ada 95.