From: vinoski@ch.apollo.hp.com (Stephen Vinoski)
Newsgroups: comp.std.c++
Subject: Re: return values and destructors
Date: 13 Nov 92 03:34:23 GMT
Organization: Hewlett-Packard Corporation, Chelmsford, MA

In article <1992Nov12.132254.1@vax1.bham.ac.uk> mccauleyba@vax1.bham.ac.uk (Brian McCauley) writes:
>I beg to differ I think that the behaviour of Cfront is a bug and that the
>statement in the ARM is unambiguous if a little opaque. The redundant copy will
>only be avoidable in cases where the return type is an inbuilt type and the
>overhead of evaluating this before calling destructors and storing it away
>somewhere is minimal. Also the return expression would have to not contain any
>class types or even function calls as any class operation even on a object that
>is not local may legitamately make use of local objects. (Consider a class that
>has a static `population' member that is the total number of objects currently
>existing. I agree the original example was contrived but I don't think it
>should break a good complier. 

Actually the example is less contrived than you think.  Consider
Stroustrup's "resource acquisition is initialization" idiom mentioned
in his 2nd edition (sorry, I don't have it handy, otherwise I'd quote
chapter and page).  This idiom calls for resources (heap storage,
files, etc.) to be acquired via constructors and released via
destructors, which is a useful approach especially when exceptions are
involved.  Anyway, consider a mutex lock class that locks a mutex in
its constructor and unlocks it in its destructor:

Foo
func()
{
    static Foo foo;
    Lock lock(foo.mutex);
    // ...
    return foo;
}

In the presence of exceptions, this approach guarantees that locks are
always released.

Clearly the programmer wants the value that foo has in the return
statement to be returned to the caller.  Unfortunately, the current
language definition does not guarantee that this will happen.  What
could happen in this case is that the Lock object's destructor will
execute when it goes out of scope, and the value of foo might change
due to another thread of control before it is copied out to the
caller.  In a multi-threaded environment, this is quite possible.

There is also a similar potential problem with exceptions and throw
statements; what happens if the value of the thrown object is affected
by the destructors executed due to stack unwinding before the throw
actually occurs?

>In article <1dsevkINNe8t@agate.berkeley.edu>, jbuck@forney.berkeley.edu (Joe Buck) writes:
>Again, I don't think so.  I'm not willing to pay the cost of the
>extra copy operation that might be required were the committee to
>force your example to work.  It's simply not worth slowing down reasonable
>C++ programs to have defined behavior on your unreasonable example (it is
>cute, though).  There is already precedent: the ANSI
>C standard leaves the order of side effects explicitly undefined in
>examples like
>
>	void func(int,int);
>	int i = 3;
>	func(i++,i++);
>

I don't see how the fact that the order of side effects within a
single statement is unspecified has anything at all to do with the
problem I've brought up.  My problem involves side effects across
multiple statements.  Aren't ANSI C "sequence points" designed to
ensure that compilers behave identically in these situations?

The "resource acquisition is initialization" approach is already very
common in C++.  In fact, the use of constructors and destructors in
this manner is typically touted as being a strong feature of the
language.  Unless the behavior of return statements vs. the
side-effects of destructors is specified by the language, I'm afraid
that this idiom becomes useless for a large class of programs.

-steve
Newsgroups: comp.std.c++,comp.lang.c++