[comp.std.c++] Order of destructor calls for auto objects

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.''