jgro@lia (Jeremy Grodberg) (12/15/90)
I cannot find anywhere in the ARM where the order of destruction of
automatic objects is defined. Am I missing something? If not, I propose
that the ANSI standard add the following to section 6.6 of the ARM:
Objects are guaranteed to be destroyed in the reverse of their order
of creation.
Take, for exampe, the following block:
{
foo a;
foo b;
foo c;
count << "Hello world\n";
}
We are already guaranteed that a will be constructed first, then b, then c,
according to section 6.7. I want to also have the guarantee that on
exit from scope, first c will be destroyed, then b, then a. This has
important implications for objects which reference other objects.
It appears that CFront 2.0 (from AT&T) already does this, but I would
like it to be formalized.
Comments?
--
Jeremy Grodberg "I don't feel witty today. Don't bug me."
jgro@lia.com
martino@logitek.co.uk (Martin O'Nions) (12/19/90)
jgro@lia (Jeremy Grodberg) writes: >I cannot find anywhere in the ARM where the order of destruction of >automatic objects is defined. Am I missing something? If not, I propose >that the ANSI standard add the following to section 6.6 of the ARM: > Objects are guaranteed to be destroyed in the reverse of their order > of creation. >We are already guaranteed that a will be constructed first, then b, then c, >according to section 6.7. I want to also have the guarantee that on >exit from scope, first c will be destroyed, then b, then a. This has >important implications for objects which reference other objects. >It appears that CFront 2.0 (from AT&T) already does this, but I would >like it to be formalized. As a traditionalist (i.e. a C programmer who views C++ as a interesting sideline rather then the future of the world) I can't honestly see that the destruction of the object is the correct phrase for your ammendment. Would it not be more accurate to refer to "the object destructor call", which is a tidy-up operation targetted at (for example) freeing of heap allocated memory, rather then the destruction of the object itself, which is likely to ocurr on an out-of-scope by the stack pointer being shifted to simultaneously "destroy" ALL the objects. Semantics I will agree, but we are talking about an ammendment to the defined standard... P.S. is there really a case where there it is essential that one knows the order of destruction, or is it just a case of planning inter-object references in an appropriate way - this is NOT a flame, just a query. Martin -- DISCLAIMER: All My Own Work (Unless stated otherwise) -------------------------------------------------------------------------- Martin O'Nions Logitek Group Support martino@logitek.co.uk -------------------------------------------------------------------------- There's been an accident they said/Your servant's cut in half - he's dead! Indeed said Mr Jones, then please/Send me the half that's got my keys. (Harry Graham - Ruthless Rhymes for Heartless Homes)
dag@control.lth.se (Dag Bruck) (12/20/90)
In article <martino.661601979@krypton> martino@logitek.co.uk (Martin O'Nions) writes: > >P.S. is there really a case where there it is essential that one knows >the order of destruction, or is it just a case of planning inter-object >references in an appropriate way - this is NOT a flame, just a query. Yes, order of destruction can be essential in some applications. In "Exception Handling for C++," Koenig and Stroustrup describe the object-as-resource-allocator technique (Proc. C++ at Work, 1989). class Resource { .... }; class Lock { public: Lock(Resource& r) : res(r) { res.allocate(); } ~Lock() { res.free(); } private: Resource& res; }; The idea is that creating a Lock object allocates some resource, which is then freed when the Lock object is destroyed. void f(Resource& x) { Lock now(x); x.manipulate(); int& i = x.data; // .... } The Lock object `now' is destroyed when leaving function f, like any other local variable, so `x' is automatically freed. The beauty is that this technique works even if we get an exception somewhere. Another advantage is that you cannot forget to free a locked resource, because you don't have to write the call explicitly. The integer reference `i' clearly depends on `x' being locked, so here is an example where I want objects to be destroyed in reverse order of construction. This is of course a trivial example, but it is not difficult to imagine a case where the locking property is essential. A related problem is when temporary objects are destroyed. In many cases the compiler may create temporary objects, and the user has no control of when the temporaries are created or destroyed. Unfortunately, the current language definition does not give any constraints at all. Using Cfront, I discovered that the approach above worked in a particular case, but the "equivalent" code without using a Lock object did not. The C++ standardization committee has noted this problem, and will surely resolve the issue. Dag M. Bruck -- Department of Automatic Control E-mail: dag@control.lth.se Lund Institute of Technology P. O. Box 118 Phone: +46 46-104287 S-221 00 Lund, SWEDEN Fax: +46 46-138118
bs@alice.att.com (Bjarne Stroustrup) (12/21/90)
About order of destructor calls. A quick glance in the index of the ARM tells me: Destructors for global objects within the same compilation unit are destroyed in reverse order of their construction. Destructors for class members are exceuted in reverse order of their construction. Destructors for base classes are exceuted in reverse order of their construction. Destructors for array elements are executed in reverse order of their construction. The guarantee that I had expected to find but didn't: Destructors for named auto variables are executed in reverse order of their construction. If it is not there it is an oversight that ought to be remedied. I believe this to be non-controversial and that all compilers in fact guarantees this. I believe that this guarantee could also be given Destructors for local static variables are executed in reverse order of their construction. but that this might be less obvious and therefore possibly controversial. I'm sure X3J16 will consider this issue. Note that complete determinism is probably not possible since the order of evaluation or sub-expressions is not guaranteed by C or C++. This implies that guarantees about the order of destruction of temporaries are hard to make. Also, the order of construction and destruction of global variables in different compilation units are hard to guarantee. Given the diversity of linkers and in particular the possibility of dynamic linking this problem is fundamentally hard.
horstman@sjsumcs.sjsu.edu (Cay Horstmann) (12/22/90)
In article <1990Dec20.074232.9416@lth.se> dag@control.lth.se (Dag Bruck) writes: > >In "Exception Handling for C++," Koenig and Stroustrup describe the >object-as-resource-allocator technique (Proc. C++ at Work, 1989). > >The idea is that creating a Lock object allocates some resource, which >is then freed when the Lock object is destroyed. > >The Lock object `now' is destroyed when leaving function f, like any >other local variable, so `x' is automatically freed. The beauty is >that this technique works even if we get an exception somewhere. >Another advantage is that you cannot forget to free a locked resource, >because you don't have to write the call explicitly. > Indeed this is very true. It is essential that destruction is guaranteed to occur in reverse order of construction. FURTHERMORE, it is also very important that objects get destroyed AS SOON AS POSSIBLE. Many current implementations do not do that--unused objects, especially temporary objects, may linger for quite a while. Cay
dag@control.lth.se (Dag Bruck) (12/24/90)
In article <1990Dec21.192649.1100@sjsumcs.sjsu.edu> horstman@sjsumcs.SJSU.EDU (Cay Horstmann) writes: >In article <1990Dec20.074232.9416@lth.se> dag@control.lth.se (Dag Bruck) writes: >> >>The idea is that creating a Lock object allocates some resource, which >>is then freed when the Lock object is destroyed. > >FURTHERMORE, it is also very important that objects get destroyed AS SOON >AS POSSIBLE. Many current implementations do not do that--unused objects, >especially temporary objects, may linger for quite a while. Well, "as soon as possible" may not be the ultimate goal for real-time programmers; the most important requirement is that it SHOULD BE DEFINED! You can imagine several alternatives: 1. As soon as possible. 2. At the end of the current expression. 3. At end of statement. 4. At end of block. 5. At end of function. Only #5 is completely useless. I think my personal bias is for #2 or #3, but this has to discussed further. The reason why #1 is not the best alternative is that it reduces the opportunity for optimization (so I believe); I have also heard the argument that it would break a lot of existing code that bends the rules slightly. The standard should also specify when a temporary may be created; this is natural requirement that often gets overlooked in the debate. And finally, Merry Christmas and a Happy New Year. Dag M Bruck -- Department of Automatic Control E-mail: dag@control.lth.se Lund Institute of Technology P. O. Box 118 Phone: +46 46-104287 S-221 00 Lund, SWEDEN Fax: +46 46-138118
jgro@lia (Jeremy Grodberg) (01/01/91)
In article <1990Dec21.192649.1100@sjsumcs.sjsu.edu> horstman@sjsumcs.SJSU.EDU (Cay Horstmann) writes: >In article <1990Dec20.074232.9416@lth.se> dag@control.lth.se (Dag Bruck) writes: >> >>In "Exception Handling for C++," Koenig and Stroustrup describe the >>object-as-resource-allocator technique (Proc. C++ at Work, 1989). >> >>The idea is that creating a Lock object allocates some resource, which >>is then freed when the Lock object is destroyed. >>[...] >Indeed this is very true. It is essential that destruction is guaranteed >to occur in reverse order of construction. > >FURTHERMORE, it is also very important that objects get destroyed AS SOON >AS POSSIBLE. Many current implementations do not do that--unused objects, >especially temporary objects, may linger for quite a while. How would you define "possible". There is no way for a compilier to know when you are done with an object, other than when it goes out of scope, and thus you have no way of accessing it. If you use an object as a resource allocator, then you may never ever reference the object after creating it. Suppose, for example, I have a semaphore object which simply waits until it can capture a semaphore, and holds the semaphore until it (the object) is destroyed. It would be perfectly reasonable to write a function like: void print(ImageType& image) { semaphore printer(PRINTER_KEY); // capture the printer access semaphore // ...do a lot of stuff involved with printing. } Such a construct can only be useful if we are guaranteed that "printer" will be constructed at the beginning of the function (which we are, by E&S 6.7), and destroyed at the end of the function (i.e. where "printer" goes out of scope). E&S 6.6 does guarantee that destructors will be called when objects go out of scope, and I strongly believe that they should not be destroyed any earlier. E&S 12.2 requires temporaries to be destroyed before the "exit from the scope in which the temporary is created." It would probably be a good idea to upgrade the observation that "If [only] the value of a temporary is fetched, that temporary is then dead and can be destroyed immediately" to a full-fledged requirement that such destruction take place before the next statement is executed. -- Jeremy Grodberg "I don't feel witty today. Don't bug me." jgro@lia.com
daniel@terra.ucsc.edu (Daniel Edelson) (01/04/91)
In article <1991Jan1.015110.329@lia> jgro@lia.com (Jeremy Grodberg) writes: > >E&S 12.2 requires temporaries to be destroyed before the "exit from >the scope in which the temporary is created." .... >-- >Jeremy Grodberg >jgro@lia.com Unless, of course, a reference is bound to the temporary, in which case the temporary must not be destroyed until the reference is. This may require a temporary to exist beyond the scope in which it is constructed. For example: struct T { }; struct U { T & tr; U(T & r) : tr(r) { } }; T maketemp() { T t; return t; } void foo() { U * p = new U(maketemp()); return p; } main() { U * p = foo(); } In this example an object of type U gets created containing a reference to a temporary T. How is the compiler to know when it can destroy the temporary? This looks like a rather ugly problem. Cfront seems to copy the temporary to the outer scope. So in fact, the temporary gets destroyed more than once. This example is not an error because it is also constructed more than once, once with the normal constructor and once with the copy constructor. But woe to the programmer who neglects to build reference counts into his/her locks! I'm sure than when aliases (pointers) are introduced a large number of cfront (and other) compiler bugs are revealed based on lifetime of temporaries. --- Daniel Edelson | C++ virtual garbage recycler: daniel@cis.ucsc.edu, or | ``Recycle your garbage. Please don't ...!sun!practic!peren!daniel | make me come and copy after you.''
daniel@terra.ucsc.edu (Daniel Edelson) (01/04/91)
In his last article daniel@terra.ucsc.edu (Daniel Edelson) writes:
<<<stuff that should have gone to comp.lang.c++ rather
than comp.std.c++, please excuse...>>>
---
Daniel Edelson | C++ virtual garbage recycler:
daniel@cis.ucsc.edu, or | ``Recycle your garbage. Please don't
...!sun!practic!peren!daniel | make me come and copy after you.''