* virtual destructors @ 2003-04-22 14:18 kat-Zygfryd 2003-04-22 14:27 ` Stephen Leake 2003-04-22 17:28 ` Matthew Heaney 0 siblings, 2 replies; 17+ messages in thread From: kat-Zygfryd @ 2003-04-22 14:18 UTC (permalink / raw) How can I define a virtual destructor in Ada95? Zygfryd ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 14:18 virtual destructors kat-Zygfryd @ 2003-04-22 14:27 ` Stephen Leake 2003-04-22 14:56 ` kat-Zygfryd 2003-04-22 17:28 ` Matthew Heaney 1 sibling, 1 reply; 17+ messages in thread From: Stephen Leake @ 2003-04-22 14:27 UTC (permalink / raw) "kat-Zygfryd" <6667@wp.pl> writes: > How can I define a virtual destructor in Ada95? The nearest equivalent of C++ destructors is achieved by deriving from a type declared in Ada.Finalization. Note that it is not a very close equivalent, so to be really helpful, we need to know in a larger sense what you are trying to do. -- -- Stephe ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 14:27 ` Stephen Leake @ 2003-04-22 14:56 ` kat-Zygfryd 2003-04-22 15:45 ` kat-Zygfryd 2003-04-22 17:33 ` virtual destructors Matthew Heaney 0 siblings, 2 replies; 17+ messages in thread From: kat-Zygfryd @ 2003-04-22 14:56 UTC (permalink / raw) "Stephen Leake" <Stephe.Leake@nasa.gov> wrote in message news:u1xzuehed.fsf@nasa.gov... > The nearest equivalent of C++ destructors is achieved by deriving from > a type declared in Ada.Finalization. Note that it is not a very close > equivalent, so to be really helpful, we need to know in a larger sense > what you are trying to do. > > -- > -- Stephe I want to have destructors working on access' to class wide types, so that when passed a variable, an actual type destructor was called, not a one overloaded for the specific type. example: type Base is tagged record ... end record; type PBase is access Base; type CBase is access Base'Class; type Derived is new Base with record ... end record; type PDerived is access Derived; type CDerived is access Derived'Class; procedure Free is new Ada.Unchecked_Deallocation(Base,PBase); procedure Free is new Ada.Unchecked_Deallocation(Derived,PDerived); procedure Dispose(self: in out CBase) is begin --? self := null; end Dispose; procedure Dispose(self: in out CDerived) is begin -- ? self := null; end Dispose; declare B: CBase := new Base; D: CBase := new Derived; begin Dispose(B); Dispose(D); end; I want both Disposes to work as they should, what should I fill them with? Zygfryd ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 14:56 ` kat-Zygfryd @ 2003-04-22 15:45 ` kat-Zygfryd 2003-04-22 16:34 ` tmoran ` (2 more replies) 2003-04-22 17:33 ` virtual destructors Matthew Heaney 1 sibling, 3 replies; 17+ messages in thread From: kat-Zygfryd @ 2003-04-22 15:45 UTC (permalink / raw) Never mind, I didn;t thinf of directly declaring procedure Free is new Ada.Unchecked_Deallocation(Base'Class,CBase); procedure Free is new Ada.Unchecked_Deallocation(Derived'Class,CDerived); Another question: are there any advantages in using Ada.Finalization for objects that will always be dynamically allocated/deallocated over simply declaring a Dispose procedure, with frees object's components before freeing itself? Zygfryd ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 15:45 ` kat-Zygfryd @ 2003-04-22 16:34 ` tmoran 2003-04-22 21:32 ` Robert A Duff 2003-04-22 17:18 ` Stephen Leake 2003-04-22 17:31 ` virtual destructors - doesn't seem to work kat-Zygfryd 2 siblings, 1 reply; 17+ messages in thread From: tmoran @ 2003-04-22 16:34 UTC (permalink / raw) > Another question: are there any advantages in using > Ada.Finalization for objects that will always be dynamically > allocated/deallocated over simply declaring a Dispose procedure, The compiler will make sure Finalize is called, whereas the programmer will sooner or later forget to call Dispose. ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 16:34 ` tmoran @ 2003-04-22 21:32 ` Robert A Duff 0 siblings, 0 replies; 17+ messages in thread From: Robert A Duff @ 2003-04-22 21:32 UTC (permalink / raw) tmoran@acm.org writes: > > Another question: are there any advantages in using > > Ada.Finalization for objects that will always be dynamically > > allocated/deallocated over simply declaring a Dispose procedure, > The compiler will make sure Finalize is called, whereas the programmer > will sooner or later forget to call Dispose. But the Finalize is just doing some Unchecked_Deallocations (according to the OP), so it doesn't really matter. If you forget to Dispose, then it will Dispose just before program exit, which is not really useful. If the Finalize were doing something important, then yes, the Ada implementation makes sure it happens. But for library-level access types (which is 99.9% of all access types, I suspect), it happens very late in the game if you don't explicitly Dispose. I think the truth is that finalization is much more useful for stack objects than for heap objects. In Java, where everything's a heap object, and deallocation is done by GC, finalization (quite a complex feature) is nigh unto useless, which is rather a shame. You can't do most of the useful tricks that Ada and C++ allow, like locking/unlocking a resource via finalization. - Bob ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 15:45 ` kat-Zygfryd 2003-04-22 16:34 ` tmoran @ 2003-04-22 17:18 ` Stephen Leake 2003-04-22 16:34 ` Simon Wright 2003-04-23 15:16 ` Matthew Heaney 2003-04-22 17:31 ` virtual destructors - doesn't seem to work kat-Zygfryd 2 siblings, 2 replies; 17+ messages in thread From: Stephen Leake @ 2003-04-22 17:18 UTC (permalink / raw) "kat-Zygfryd" <6667@wp.pl> writes: > Never mind, I didn;t thinf of directly declaring > > procedure Free is new Ada.Unchecked_Deallocation(Base'Class,CBase); > procedure Free is new Ada.Unchecked_Deallocation(Derived'Class,CDerived); You also need to ensure that both access types use the same storage pool. Something like: for CDerived'Storage_Pool use CBase'Storage_Pool. > Another question: are there any advantages in using Ada.Finalization > for objects that will always be dynamically allocated/deallocated > over simply declaring a Dispose procedure, with frees object's > components before freeing itself? Yes. The compiler ensures that Finalize is called. Users will forget to call Dispose. -- -- Stephe ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 17:18 ` Stephen Leake @ 2003-04-22 16:34 ` Simon Wright 2003-04-22 19:57 ` Stephen Leake 2003-04-23 15:16 ` Matthew Heaney 1 sibling, 1 reply; 17+ messages in thread From: Simon Wright @ 2003-04-22 16:34 UTC (permalink / raw) Stephen Leake <Stephe.Leake@nasa.gov> writes: > You also need to ensure that both access types use the same storage > pool. Something like: > > for CDerived'Storage_Pool use CBase'Storage_Pool. I don't see why? there was no attempt I could see to convert between the classwide pointer types? ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 16:34 ` Simon Wright @ 2003-04-22 19:57 ` Stephen Leake 2003-04-22 20:19 ` Simon Wright 2003-04-22 21:23 ` Robert A Duff 0 siblings, 2 replies; 17+ messages in thread From: Stephen Leake @ 2003-04-22 19:57 UTC (permalink / raw) Simon Wright <simon@pushface.org> writes: > Stephen Leake <Stephe.Leake@nasa.gov> writes: > > > You also need to ensure that both access types use the same storage > > pool. Something like: > > > > for CDerived'Storage_Pool use CBase'Storage_Pool. > > I don't see why? there was no attempt I could see to convert between > the classwide pointer types? In general, the compiler can use different storage pools for each access type. If you do: A : CBase := new Derived'(); and then later: Free (A); The compiler might allocation from CDerived_Storage_Pool, and then deallocate from CBase_Storage_Pool. Very bad. -- -- Stephe ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 19:57 ` Stephen Leake @ 2003-04-22 20:19 ` Simon Wright 2003-04-22 21:23 ` Robert A Duff 1 sibling, 0 replies; 17+ messages in thread From: Simon Wright @ 2003-04-22 20:19 UTC (permalink / raw) Stephen Leake <Stephe.Leake@nasa.gov> writes: > Simon Wright <simon@pushface.org> writes: > > > Stephen Leake <Stephe.Leake@nasa.gov> writes: > > > > > You also need to ensure that both access types use the same storage > > > pool. Something like: > > > > > > for CDerived'Storage_Pool use CBase'Storage_Pool. > > > > I don't see why? there was no attempt I could see to convert between > > the classwide pointer types? > > In general, the compiler can use different storage pools for each > access type. > > If you do: > > A : CBase := new Derived'(); > > and then later: > > Free (A); > > The compiler might allocation from CDerived_Storage_Pool, and then > deallocate from CBase_Storage_Pool. Very bad. Indeed, I hadn't spotted that particular problem (I was looking at the procedures, not the main program). Sorry. ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 19:57 ` Stephen Leake 2003-04-22 20:19 ` Simon Wright @ 2003-04-22 21:23 ` Robert A Duff 1 sibling, 0 replies; 17+ messages in thread From: Robert A Duff @ 2003-04-22 21:23 UTC (permalink / raw) Stephen Leake <Stephe.Leake@nasa.gov> writes: > In general, the compiler can use different storage pools for each > access type. True, but in the following example, there is only one access type: CBase. So both the "new" and the "Free" will be referring to the same storage pool, namely CBase'Storage_Pool, so all is well. > If you do: > > A : CBase := new Derived'(); > > and then later: > > Free (A); > > The compiler might allocation from CDerived_Storage_Pool, and then > deallocate from CBase_Storage_Pool. Very bad. To get in trouble, you have to do a type conversion. - Bob ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 17:18 ` Stephen Leake 2003-04-22 16:34 ` Simon Wright @ 2003-04-23 15:16 ` Matthew Heaney 1 sibling, 0 replies; 17+ messages in thread From: Matthew Heaney @ 2003-04-23 15:16 UTC (permalink / raw) Stephen Leake <Stephe.Leake@nasa.gov> wrote in message news:<u3ckacuwv.fsf@nasa.gov>... > "kat-Zygfryd" <6667@wp.pl> writes: > > Yes. The compiler ensures that Finalize is called. Users will forget > to call Dispose. Another possibility is to use a handle of some kind (a la the C++ auto_ptr class), that claims ownership of the access object, and automatically calls Free when the handle object is itself finalized. The Charles library has an Access_Control package that does just that, e.g. declare P1 : Pointer_Type; --from Charles.Access_Control P2 : Pointer_Type; begin Initialize (P1, new T); --claim ownership Assign (Target => P2, Source => P1); --transfer ownership Op (+P2); --use access object end; --P2 will Free object You can even use this in a factory function: function New_T return T_Access is P : Pointer_Type; begin Initialize (P, new T); --claim ownership --do some stuff that may raise an exception return Release (P); --relinguish ownership end; If the factory function New_T raises an exception prior to reaching the return, then pointer P will free the allocated object during its finalization, because it still owns the object. If we do reach the return statement, then we tell P to release the object, so P does nothing during its finalization, and ownership of the allocated object is transfered to the caller of New_T. The party to whom New_T transfers ownership might indeed even be another access control object: declare P : Pointer_Type; begin Initialize (P, Value => New_T); --now use +P end; --free object during finalization of P The Charles.Access_Control component is part of the Charles library, available at my website: http://home.earthlink.net/~matthewjheaney/charles/ ^ permalink raw reply [flat|nested] 17+ messages in thread
* virtual destructors - doesn't seem to work 2003-04-22 15:45 ` kat-Zygfryd 2003-04-22 16:34 ` tmoran 2003-04-22 17:18 ` Stephen Leake @ 2003-04-22 17:31 ` kat-Zygfryd 2003-04-22 17:32 ` Simon Wright 2 siblings, 1 reply; 17+ messages in thread From: kat-Zygfryd @ 2003-04-22 17:31 UTC (permalink / raw) It doesn't seem to work :( Look: -- errata to previous code type CBase is access all Base'Class; type CDerived is access all Derived'Class; -- /errata procedure Dispose(self: in out CBase) is begin Put_Line("CBase.Dispose"); end Dispose; procedure Dispose(self: in out CDerived) is begin Put_Line("CDerived.Dispose"); end Dispose; function create_derived return CDerived is begin return new Derived; end create_derived; declare B: CBase := CBase(create_derived); begin Dispose(B); -- outputs "CBase.Dispose" end; what am I doing wrong? Zygfryd ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors - doesn't seem to work 2003-04-22 17:31 ` virtual destructors - doesn't seem to work kat-Zygfryd @ 2003-04-22 17:32 ` Simon Wright 0 siblings, 0 replies; 17+ messages in thread From: Simon Wright @ 2003-04-22 17:32 UTC (permalink / raw) "kat-Zygfryd" <6667@wp.pl> writes: > It doesn't seem to work :( > Look: > > -- errata to previous code > type CBase is access all Base'Class; > type CDerived is access all Derived'Class; > -- /errata > > procedure Dispose(self: in out CBase) is > begin > Put_Line("CBase.Dispose"); > end Dispose; > > procedure Dispose(self: in out CDerived) is > begin > Put_Line("CDerived.Dispose"); > end Dispose; > > function create_derived return CDerived is > begin > return new Derived; > end create_derived; > > declare > B: CBase := CBase(create_derived); > begin > Dispose(B); -- outputs "CBase.Dispose" > end; > > what am I doing wrong? Your Dispose operations aren't dispatching operations (they take a parameter of a type which you have declared, not an anonymous access-to-tagged-type). So when you say Dispose(B); it is Dispose(self: in out CBase) that gets called. type T is tagged private; type T_P is access T; type T_C is access T'Class; procedure P1 (P : T); -- dispatches procedure P2 (P : access T); -- dispatches procedure P3 (P : T_P); -- doesn't dispatch procedure P4 (P : T_C); -- doesn't dispatch I'm a bit puzzled by what you're trying to do. I would have expected your procedure Dispose(self: in out CDerived) to need to call your procedure Dispose(self: in out CBase) (because a Derived is a Base and the Base part will need finalizing). I would have thought what you'd really like to do is have just the one classwide pointer type CBase and have the correct finalization called. Matt has shown how to do that .. having two classwide pointer types seems likely to cause trouble. ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 14:56 ` kat-Zygfryd 2003-04-22 15:45 ` kat-Zygfryd @ 2003-04-22 17:33 ` Matthew Heaney 1 sibling, 0 replies; 17+ messages in thread From: Matthew Heaney @ 2003-04-22 17:33 UTC (permalink / raw) "kat-Zygfryd" <6667@wp.pl> wrote in message news:<b83l69$7bk$1@news.onet.pl>... > > I want to have destructors working on access' to class wide types, > so that when passed a variable, an actual type destructor was > called, not a one overloaded for the specific type. > > example: > > type Base is tagged record ... end record; > type PBase is access Base; > type CBase is access Base'Class; > > type Derived is new Base with record ... end record; > type PDerived is access Derived; > type CDerived is access Derived'Class; > > procedure Free is new Ada.Unchecked_Deallocation(Base,PBase); > procedure Free is new Ada.Unchecked_Deallocation(Derived,PDerived); > > procedure Dispose(self: in out CBase) is > begin > --? > self := null; > end Dispose; > > procedure Dispose(self: in out CDerived) is > begin > -- ? > self := null; > end Dispose; > > declare > B: CBase := new Base; > D: CBase := new Derived; > begin > Dispose(B); > Dispose(D); > end; > > I want both Disposes to work as they should, what should I fill them with? Give the class a primitive operation that accepts an access parameter: procedure Do_Dispose (B : access Base) is ...; procedure Do_Dispose (D : access Derived) is ...; procedure Dispose (B : in out CBase) is begin if B /= null then Do_Dispose (B); --dispatches B := null; end if; end Dispose; ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 14:18 virtual destructors kat-Zygfryd 2003-04-22 14:27 ` Stephen Leake @ 2003-04-22 17:28 ` Matthew Heaney 2003-04-22 17:40 ` kat-Zygfryd 1 sibling, 1 reply; 17+ messages in thread From: Matthew Heaney @ 2003-04-22 17:28 UTC (permalink / raw) "kat-Zygfryd" <6667@wp.pl> wrote in message news:<b83ivk$29l$1@news.onet.pl>... > How can I define a virtual destructor in Ada95? There are two ways. I. Use controlled types Have your type (privately) derived from Controlled, and then use Unchecked_Deallocation to delete it through a class-wide type: package P is type T is tagged private; type T_Class_Access is access all T'Class; procedure Op (O : access T); private type T is new Controlled with record ...; end record; procedure Finalize (O : in out T); end P; package P.C is type NT is new T with private; private type NT is new T with record ...; end record; procedure Finalize (O : in out NT); end P.C; procedure Free is new Ada.Unchecked_Deallocation (T'Class, T_Class_Access); declare O1 : T_Class_Access := new T; O2 : T_Class_Access := new NT; begin Free (O1); --call P.Finalize Free (O2); --call P.C.Finalize end; What's happening here is that the Ada run-time system knows that types in T'Class are controlled, so that when you use Unchecked_Deallocation, it calls the Finalize operation for the object being deleted. So in this sense destructors are already dispatching in Ada95, and there's nothing special you need to do. Note that Finalize operations for a class are typically implemented by performing actions specific to the derived type, and then forwarding the call to the parent type, e.g. procedure Finalize (O : in out NT) is begin --do NT stuff here Finalize (T (O)); --"view conversion" to call T's Finalize end; Also note that if any components are controlled, they too will get finalized in the normal way. For example, if we had implemented T this way: private type T is tagged record -- not itself controlled Vector : Vector_Subtype; end record; Here, the Vector_Subtype is an instance of Charles.Vectors.Unbounded.Container_Type, which is implemented as a controlled type. So when an object of T is deallocated, controlled finalization of its component(s) occurs, which means here that the Finalize for Vector is called automatically. What this really means is that you may not need a virtual destructor at all. II. Use an explicit dispatching destructor In this case you're going to assume some of the duties yourself, by explicitly calling a dispatching operation. What many Ada programmers don't seem to realize is that operations that have access parameters are primitive for a type, meaning that they are inherited during derivations, and (more importantly here) dispatch when called with a parameter whose type is class-wide. Let's modify our class above to add a private primitive operation: package P is type T (<>) is tagged limited private; type T_Class_Access is access all T'Class; function New_T return T_Class_Access; procedure Free (O : in out T_Class_Access); private type T is tagged limited record ...; procedure Do_Free (O : access T); --declare primitive end P; package P.C is type NT (<>) is new T with private; function New_NT return T_Class_Access; private type NT is new T with record ...; procedure Do_Free (O : access NT); --override primitive end P.C; What we want to do is hand off the pointer to the object to the destructor, but we need to the destructor to dispatch. The public Free operation is implemented this way: procedure Free (O : in out T_Class_Access) is begin if O /= null then Do_Free (O); -- dispatches O := null; end if; end Free; The object designated by pointer O has type T'Class, which means that if called with a primitive operation, then dispatching will occur. Do_Free is primitive, so it dispatches. For example: declare O1 : T_Class_Access := New_T; O2 : T_Class_Access := New_NT; begin Free (O1); --internal dispatch to T's Do_Free Free (O2); --internal dispatch to NT's Do_Free end; The Free operation is an example of the "template method" pattern. The Do_Free operation is the "virtual destructor." ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: virtual destructors 2003-04-22 17:28 ` Matthew Heaney @ 2003-04-22 17:40 ` kat-Zygfryd 0 siblings, 0 replies; 17+ messages in thread From: kat-Zygfryd @ 2003-04-22 17:40 UTC (permalink / raw) "Matthew Heaney" <mheaney@on2.com> wrote in message news:1ec946d1.0304220928.72c2acc8@posting.google.com... > "kat-Zygfryd" <6667@wp.pl> wrote in message news:<b83ivk$29l$1@news.onet.pl>... > > How can I define a virtual destructor in Ada95? > > There are two ways. > > I. Use controlled types > > Have your type (privately) derived from Controlled, and then use > Unchecked_Deallocation to delete it through a class-wide type: > > package P is > > type T is tagged private; > > type T_Class_Access is access all T'Class; > > procedure Op (O : access T); > > private > > type T is new Controlled with record > ...; > end record; > > procedure Finalize (O : in out T); > > end P; > > > package P.C is > > type NT is new T with private; > > private > > type NT is new T with record > ...; > end record; > > procedure Finalize (O : in out NT); > > end P.C; > > procedure Free is > new Ada.Unchecked_Deallocation (T'Class, T_Class_Access); > > declare > O1 : T_Class_Access := new T; > O2 : T_Class_Access := new NT; > begin > Free (O1); --call P.Finalize > Free (O2); --call P.C.Finalize > end; > > > What's happening here is that the Ada run-time system knows that types > in T'Class are controlled, so that when you use > Unchecked_Deallocation, it calls the Finalize operation for the object > being deleted. > > So in this sense destructors are already dispatching in Ada95, and > there's nothing special you need to do. > > Note that Finalize operations for a class are typically implemented by > performing actions specific to the derived type, and then forwarding > the call to the parent type, e.g. > > procedure Finalize (O : in out NT) is > begin > --do NT stuff here > Finalize (T (O)); --"view conversion" to call T's Finalize > end; Thanks, I like that :) Hope it'll work for me Zygfryd ^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2003-04-23 15:16 UTC | newest] Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2003-04-22 14:18 virtual destructors kat-Zygfryd 2003-04-22 14:27 ` Stephen Leake 2003-04-22 14:56 ` kat-Zygfryd 2003-04-22 15:45 ` kat-Zygfryd 2003-04-22 16:34 ` tmoran 2003-04-22 21:32 ` Robert A Duff 2003-04-22 17:18 ` Stephen Leake 2003-04-22 16:34 ` Simon Wright 2003-04-22 19:57 ` Stephen Leake 2003-04-22 20:19 ` Simon Wright 2003-04-22 21:23 ` Robert A Duff 2003-04-23 15:16 ` Matthew Heaney 2003-04-22 17:31 ` virtual destructors - doesn't seem to work kat-Zygfryd 2003-04-22 17:32 ` Simon Wright 2003-04-22 17:33 ` virtual destructors Matthew Heaney 2003-04-22 17:28 ` Matthew Heaney 2003-04-22 17:40 ` kat-Zygfryd
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox