Thread View: comp.std.c++
5 messages
5 total messages
Started by Stephen Vinoski
Wed, 11 Nov 1992 23:36
return values and destructors
Author: Stephen Vinoski
Date: Wed, 11 Nov 1992 23:36
Date: Wed, 11 Nov 1992 23:36
84 lines
2652 bytes
2652 bytes
In section 6.6.3 on page 90 the ARM states:
"A return statement with an expression can be used only in functions
returning a value; the value of the expression is returned to the
caller of the function."
Given the statement
return foo;
where foo is an object with some value, does the statement from the
ARM mean that the compiler *must* return the value that foo has in the
return statement, rather than the value foo might have at some point
after the destructors for local automatic objects have been executed?
Consider this example:
template<class T>
class A
{
public:
A(T *p) : ptr(p) {}
~A() {if (ptr) *ptr = 0;}
private:
T *ptr;
};
int
func1()
{
int x = 10;
A<int> a(&x);
return x;
}
Under cfront 3.0.1, func1() returns 0, not 10, even though the value of
x in the return statement is clearly 10. The value of x is changed to
0 due to the destructor of the A object going out of scope at the end
of the function.
What's more interesting is that if a func2() is created that returns
by value an object of a class type with a constructor and destructor,
the desired value is returned because of the generation of a
temporary:
class B
{
public:
B(int x) : val(x) {}
~B() {}
void operator=(int x) {val = x;}
operator int() {return val;}
private:
int val;
};
B
func2()
{
B b(10);
A<B> a(&b); // same A as above
return b;
}
In this case, func2() returns a B whose val member has a value of 10,
due to the generation of a temporary, even though the same A
destructor sets the value to 0.
Shouldn't both of these cases perform the same way? Is this a cfront
bug? Shouldn't the value of the return expression be returned to the
caller regardless of side effects of destructors for auto objects?
Does the language enforce any of this? The pre-Boston mailing doesn't
seem to discuss this any differently than the ARM does.
thanks,
-steve
--
Steve Vinoski (508)436-5904 vinoski@apollo.hp.com
Distributed Object Computing Program
Hewlett-Packard, Chelmsford, MA 01824 These are my opinions.
Re: return values and destructors
Author: Joe Buck
Date: Thu, 12 Nov 1992 02:19
Date: Thu, 12 Nov 1992 02:19
50 lines
2210 bytes
2210 bytes
In article <BxKs87.417@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes: >"A return statement with an expression can be used only in functions >returning a value; the value of the expression is returned to the >caller of the function." > >Given the statement > > return foo; > >where foo is an object with some value, does the statement from the >ARM mean that the compiler *must* return the value that foo has in the >return statement, rather than the value foo might have at some point >after the destructors for local automatic objects have been executed? [ example deleted: it's designed so that the destructor of an auto variable will alter foo's value. ] Your example is the kind of thing that I think language standards should leave explicitly undefined; otherwise compiler vendors will have to generate worse code just in case someone like you gets tricky. :-) >Shouldn't both of these cases perform the same way? I don't think so (if that means a change is required in the draft standard, so be it). >Is this a cfront bug? 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++); The ARM statement needs amendment in any case, as it says elsewhere that the copy constructor is called to return objects, and there is no guarantee that the user will write a proper copy constructor. -- Joe Buck jbuck@ohm.berkeley.edu
Re: return values and destructors
Author: Brian McCauley
Date: Thu, 12 Nov 1992 13:22
Date: Thu, 12 Nov 1992 13:22
51 lines
2850 bytes
2850 bytes
In article <1dsevkINNe8t@agate.berkeley.edu>, jbuck@forney.berkeley.edu (Joe Buck) writes:
> In article <BxKs87.417@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes:
>>"A return statement with an expression can be used only in functions
>>returning a value; the value of the expression is returned to the
>>caller of the function."
>>
>>Given the statement
>>
>> return foo;
>>
>
> [ example deleted: it's designed so that the destructor of an auto
> variable will alter foo's value. ]
>
> Your example is the kind of thing that I think language standards should
> leave explicitly undefined; otherwise compiler vendors will have to
> generate worse code just in case someone like you gets tricky. :-)
>
[...]
>>Is this a cfront bug?
>
> 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)...
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.
Some compilers offer optimization switches with names like "assume no pointer
aliasing" which allow them to make this sort of optimisation at the risk of
miscompiling this sort of perverse example. I do not think a compiler should
make unsafe optimisations unless the programmer has invited it to do so.
--
\\ ( ) NO BULLSHIT! from BAM (Brian McCauley)
. _\\__[oo
.__/ \\ /\@ E-mail: B.A.McCauley@bham.ac.uk
. l___\\ Fax: +44 21 625 2175
# ll l\\ Snail: 197 Harborne Lane, Birmingham, B29 6SS, UK
###LL LL\\ ICBM: 52.5N 1.9W
Re: return values and destructors
Author: Jim Adcock
Date: Thu, 12 Nov 1992 22:47
Date: Thu, 12 Nov 1992 22:47
117 lines
4272 bytes
4272 bytes
In article <BxKs87.417@scd.hp.com> vinoski@ch.apollo.hp.com (Stephen Vinoski) writes:
|
|In section 6.6.3 on page 90 the ARM states:
|
|"A return statement with an expression can be used only in functions
|returning a value; the value of the expression is returned to the
|caller of the function."
First note, that like ANSI-C, C++ does not seem to require return-by-copy,
allowing instead return-by value. This in no way, however, excuses
compiler from making a copy of the returned value of the expression if
such a copy is required in order to maintain the correct value of that
return expression as evaluated in sequence and in scope.
|Given the statement
|
| return foo;
|
|where foo is an object with some value, does the statement from the
|ARM mean that the compiler *must* return the value that foo has in the
|return statement, rather than the value foo might have at some point
|after the destructors for local automatic objects have been executed?
Consider the not-uncommon case where foo is an auto-variable of the
function returning foo and foo is of a class with a dtor. This common
case demonstrates that the scope that the return value expression must
the same as the scope that the return expression is in. Otherwise foo
would be dead before evaluated. Thus evaluation of the expression
must occur before exiting scope. Dtoring occurs *at* exit of scope.
Thus the evaluation of the expression occurs before dtoring. If dtoring
then affects an evaluation that must occur before dtoring, clearly
this must be a compiler bug. If the rules were otherwise you could
not reliably return-by-value an auto-variable object of a class with a dtor.
|Consider this example:
|
|template<class T>
|class A
|{
| public:
| A(T *p) : ptr(p) {}
| ~A() {if (ptr) *ptr = 0;}
| private:
| T *ptr;
|};
|
|
|int
|func1()
|{
| int x = 10;
| A<int> a(&x);
| return x;
|}
|
|
|func1() returns 0, not 10, even though the value of
|x in the return statement is clearly 10. The value of x is changed to
|0 due to the destructor of the A object going out of scope at the end
|of the function.
As you point out the value of x in the return statement is clearly 10.
The evaluation of the return expression then is the evaluation of x
is clearly 10. The returned value is the evaluation of the return expression
is the evaluation of x is clearly 10. Anything else is clearly a bug.
|What's more interesting is that if a func2() is created that returns
|by value an object of a class type with a constructor and destructor,
|the desired value is returned because of the generation of a
|temporary:
|
|class B
|{
| public:
| B(int x) : val(x) {}
| ~B() {}
| void operator=(int x) {val = x;}
| operator int() {return val;}
| private:
| int val;
|};
|
|B
|func2()
|{
| B b(10);
| A<B> a(&b); // same A as above
| return b;
|}
|
|
|In this case, func2() returns a B whose val member has a value of 10,
|due to the generation of a temporary, even though the same A
|destructor sets the value to 0.
|
|Shouldn't both of these cases perform the same way?
Yes.
|Is this a compiler bug?
Bug.
|Shouldn't the value of the return expression be returned to the
|caller regardless of side effects of destructors for auto objects?
Yes because clearly evaluation of an expression in scope, including
the evaluation of the expression in the return statement, are evaluated
in scope, and dtor'ing doesn't happen until exiting of scope, which is later.
|Does the language enforce any of this?
If when the spec is complete, it ought to be complete and specific enough
to make these issues unarguable. These issues of return-by-value,
return-by-copy, etc, have been kicking around for many years in the
ANSI-C committee and now C++, and ought to just be nailed down once and
for all.
Re: return values and destructors
Author: Stephen Vinoski
Date: Fri, 13 Nov 1992 03:34
Date: Fri, 13 Nov 1992 03:34
74 lines
4142 bytes
4142 bytes
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
Thread Navigation
This is a paginated view of messages in the thread with full content displayed inline.
Messages are displayed in chronological order, with the original post highlighted in green.
Use pagination controls to navigate through all messages in large threads.
Back to All Threads