> [log in to unmask] wrote: > > > > (Ignoring the stuff about a temp to allow X := X; to work.) You don't need to worry about "X := X" in Ada 95. The compiler is required to suppress the calls on Finalize/Adjust if the left hand side and the right hand side denote the same object (or introduce a temp, which is generally much less efficient -- see 7.6(19)). On the other hand, normally controlled objects contain a pointer to something in the heap, and it is still possible that you might assign a controlled object from another object that points to the same heap object. Presumably that is what reference counts are supposed to handle smoothly. Stanley Allen wrote: > This example gets more confusing to me the > more I look at it. For convenience, let me > repeat the spec: > > ------------------------------- > with Ada.Finalization; use Ada.Finalization; > package Final is > > type Finis is private; > function Get_Count return Integer; > function Create return Finis; > > private > > type Finis is new Controlled with null record; > > procedure Initialize (Obj : in out Finis); > procedure Adjust (Obj : in out Finis); > procedure Finalize (Obj : in out Finis); > > Null_Finis : constant Finis := (Controlled with null record); > > end Final; > -------------------------------- > > At the point of declaration of Null_Finis, > the specs of the Init/Adj/Fin procedures for > type Finis have been declared, but the bodies > have not. (BTW, the compilers complain that > the declaration of the constant Null_Finis > above these spec declarations is illegal because > Null_Finis 'freezes' the type.) This declaration > should cause Adjust to be called for Null_Finis; > ... [eliding general discussion of what might happen] For an object initialized by an aggregate, Adjust is *not* called. This is now a required "optimization," per Ada 95 AI-00083: !standard 07.06 (21) 96-06-05 AI95-00083/02 !class binding interpretation 95-07-27 !status WG9 approved 95-06-14 !status work item (letter ballot was 10-1-0) 96-06-05 !status ARG approved (subject to letter ballot) 7-0-3 95-11-01 !status received 95-07-27 !subject Aggregates of a controlled type !summary 95-07-27 When an object of a controlled type is created and explicitly initialized with an aggregate, the aggregate is built "in place"; Finalize and Adjust are not called on the object as a whole. !question 95-07-27 If an object of a controlled type is declared in the same package as the type, and initialized with an aggregate, is Program_Error raised? (No.) !recommendation 95-07-27 When an object of a controlled type is created and explicitly initialized with an aggregate, the implementation must not create a separate anonymous object for the aggregate; it must create the value of the aggregate directly in the newly-created object. Thus, there is no assignment from the anonymous object to the newly-created object, so the Finalize and Adjust that would be done for that assignment are not done. ... > Unfortunately, I cannot determine what the effect > is of declaring such constants because when I feed > the following code to my two compilers at home (GNAT > and Aonix), the results diverge: The Aonix compiler is doing the right thing. The problem is that your type "Y" is not a controlled type, since its parent type is not visibly controlled anywhere within the immediate scope of Y. > ---------------------------------------- > with Ada.Finalization; use Ada.Finalization; > package Xxx is > type X is tagged private; > private > > type X is new Controlled with record > A : Integer; > B : Float; > end record; > > procedure Initialize (Obj : in out X); > procedure Adjust (Obj : in out X); > procedure Finalize (Obj : in out X); > > end Xxx; > ------------------------------------------ > with Text_Io; use Text_Io; > package body Xxx is > > procedure Initialize (Obj : in out X) is begin > Put_Line ("Initialize called for object of type X"); end; > procedure Adjust (Obj : in out X) is begin > Put_Line ("Adjust called for object of type X"); end; > procedure Finalize (Obj : in out X) is begin > Put_Line ("Finalize called for object of type X"); end; > > end Xxx; > ------------------------------------------- > with Xxx; use Xxx; > package Yyy is > type Y is tagged private; > Null_Y : constant Y; -- common, innocuous > private > > type Y is new X with null record; It turns out that Y is *not* controlled, even though you might think it is. X is not visibly derived from Controlled, so Y does not inherit, and cannot override, the primitive operations Initialize/Adjust/Finalize. See RM95 7.3.1(6). > procedure Initialize (Obj : in out Y); > procedure Adjust (Obj : in out Y); > procedure Finalize (Obj : in out Y); These declarations don't override anything. They are declaring procedures that just happen to be called "Initialize"/"Adjust"/"Finalize." > > -- the big question: what's happening here? > Null_Y : constant Y := (X with null record); Even *if* Y had been controlled, neither Y's Adjust nor Y's Finalize would be called, per AI-00083. In any case, X's (default) Initialize should be called, since the extension aggregate you used has an "X" part which needs to be default initialized. The RM allows the compiler to build the initial value for Null_Y "in place," so there is no need to copy and Adjust the aggregate, so all you should get is a single call on X's Initalize, passed the view conversion "X(Null_Y)." > end Yyy; > -------------------------------------------- > with Text_Io; use Text_Io; > package body Yyy is > > procedure Initialize (Obj : in out Y) is begin > Put_Line ("Initialize called for object of type Y"); end; > procedure Adjust (Obj : in out Y) is begin > Put_Line ("Adjust called for object of type Y"); end; > procedure Finalize (Obj : in out Y) is begin > Put_Line ("Finalize called for object of type Y"); end; > > end Yyy; > --------------------------------------------- > with Yyy; use Yyy; > with Text_Io; use Text_Io; > procedure Usey is -- main program > Y1 : Y; This is just a record containing a controlled part (the "X" part). That part needs to be default initialized, by calling X's Initialize. > begin > Y1 := Null_Y; This involves an assignment where the "X" part of Y is controlled. Hence, the X part of Y1 needs to be Finalized, then Null_Y is copied onto Y1, and then the X part of Y1 needs to be Adjusted. At scope exit, the X part of Y1 needs to be finalized. > end Usey; At program exit, the X part of Null_Y needs to be finalized. > ---------------------------------------------- > > Aonix (Win95 7.1) says this when I run Usey: > > Initialize called for object of type X > Initialize called for object of type X > Finalize called for object of type X > Adjust called for object of type X > Finalize called for object of type X > Finalize called for object of type X > > which can't be right. Ah, but it is, because your "Y" type is not controlled, but it contains a controlled X part. > GNAT (Win95 3.10p) says this: > > Initialize called for object of type Y > Finalize called for object of type Y > Adjust called for object of type Y > Finalize called for object of type Y > Finalize called for object of type Y > > Wierd, huh? For GNAT, there are no objects > of type X; for Aonix, no objects of type Y. > In GNAT's scheme, there is an "extra" Finalize > which matches no Initialize or Adjust. For > Aonix, they match up, but it's obviously bogus. Actually the Aonix results are not bogus. If Y had in fact been controlled, either by being declared in a child of Xxx where it could "see" that X was controlled, or by making Xxx.X visibly controlled, e.g.: type X is new Controlled with private; then the sequence you should see is: Initialize called for object of type X -- def. init of X part of Null_Y Initialize called for object of type Y -- default init of local variable Y1 Finalize called for object of type Y -- pre-assign fin. for Y1 Adjust called for object of type Y -- post-assign adj. for Y1 Finalize called for object of type Y -- end of scope for var Y1 Finalize called for object of type Y -- end of scope for Null_Y So the GNAT results are almost right if Y had been controlled (which it isn't), but the GNAT results are still missing the call on Initialize for the X part of Null_Y. Note that as good coding practice, the Initialize routine for Y should call the Initialize routine for X, the Adjust for Y should call the Adjust for X, and the Finalize for Y should call the Finalize for X. Finally, I would generally recommend you dispense with Initialize routines, unless you can't figure out a way to do the default initialization required any other way. At least on some implementations, having a user written Initialize routine can make controlled types significantly less efficient than they already are. > ... > It's not clear to me that RM 7.6(11) that the aggregate > as a whole does not cause Init/Adj to be called; AI-00083 requires that Init/Adj *not* be called for an object initialized by an aggregate. In any case, an aggregate by itself never involves a call on Initialize or Adjust for the whole aggregate, though it may involve calls on Initialize or Adjust for controlled *parts* of the aggregate. > ... > Somebody's wrong somewhere. The major problem is that Y is derived from a type (X) that is not visibly controlled. Hence, declaring Initialize/Adjust/Finalize for Y has no special effect (per RM95 7.3.1(6)). If you change the declaration of X to be type X is new Controlled with private; at least you will begin to see something like what you would expect. However, it is important to realize that Initialize/Adjust is not called on aggregates as a whole, nor on objects initialized with an aggregate. The aggregate must have the value as you want it to be seen by Finalize. Typically, this means that the reference count should be set to 1 in the heap object pointed to by a controlled object created by an aggregate. E.g.: My_Controlled'(ptr => new heap_obj(ref_count => 1, ...), ...) And for sure the controlled type should be private or a private extension, so only code inside the package can utilize aggregates. > Stanley Allen > mailto:[log in to unmask] -Tucker Taft [log in to unmask]