[gnu.g++] Generating temporaries

tiemann@YAHI.STANFORD.EDU (Michael Tiemann) (04/05/89)

I hope I am not stealing Andy Koenig's fire in answering a C++
langauge question...

   >From article <7.UUL1.2#261@persoft.UUCP>, by ericf@persoft.UUCP (Eric R. Feigenson):
   > [I'm rather new to C++, so this may be a rather naive query.  Please bear
   >  with me]
   
   Nope, good question.
   
   > I have a class M for which I want to define operator+.  My question(s) have
   > to do with how I generate the temporary that contains the result, and how
   > such a temporary gets destroyed.
   > 
   > First, if my operator+ returns an object of class M, it has to create
   > an object to return. If I understand things right, this will call
   > operator+, which will return an object of class M (which was
   > pushed onto the stack), and do a structure copy into c.  All the destructors
   > get called just fine, and no stray memory is left around, but there's the
   > overhead of the structure copy.
Well, it all depends on the compiler you are using.  I know that at
least AT&T cfront and GNU C++ are smarter than this.  What happens in
these compilers is that the caller passes the address of the place
where the new temporary should be initialized.  Depending on the way
it is initialized, there may be no overhead visible from the call to
operator+ at all:

M operator+(M x, M y)
{
  return M (x.value () + y.value ());
}

In general, if the compiler can get away with generating an invisible
temporary, it can do quite clever things.

But you may need to do hairier things, which require you to get a
handle on the value you are returning:

M operator+(M x, M y)
{
  M tmp;	// call to default constructor, if any.
  tmp.set (x.value () + y.value ())
  if (fullmoon ())
    tmp.howl ();
  return tmp;	// call to M(M&) constructor, or structure copy, sigh
}

A really smart compiler could notice that tmp was the only variable
feeding the return value, and substitute the hidden result parameter
for tmp throughout.  Dumber compilers can get around this problem by
extending the language:

M operator+(M x, M y) return tmp;	// call to default constructor
{
  tmp.set (x.value () + y.value ())
  if (fullmoon ())
    tmp.howl ();
  return tmp;	// this need do nothing, since we initialized the
		// return value in place
}

Now here's not what you want to do in any event:

   > Now I've also fiddled around with trying to return a reference to an object
   > M, in order to avoid the structure copy.  The code for operator+() has to
   > allocate a pointer to an object of type M using the "new" operator.
   > So, if we have some code that looks like:
   > 
   >     M a, b, c;
   > 
   >     // some code to give a and b values
   > 
   >     c = a + b;  // calls "M& M::operator+(M& x)"
   > 
   > and we define operator=(M&) to do the assignment, how can we free the
   > space allocated by operator+() for the temporary result?  If operator=()
   > does the "delete" then it may destroy some space that we meant to keep
   > around.  Also, in an expression such as "a + b + c", how can the
   > intermediate temporaries be freed?  The destructors get called, but how
   > can they know to delete the space on the free store?
The answer to these questions is: if you do this, you won't win.  What
you should do is try to use C++ in the way it was intended to be used.
It is your programs you should try to outsmart, not the compiler.  As
a general rule of thumb, you can safely assume that no construct was
added to C++ which would not have good efficiency.  Return-by-value is
no exception.

   > 
   > To summarize:  I want to be able to define operator+ (or any other operator
   > for that matter) using references (pointers) to avoid the overhead of
   > structure copy.  The problem seems to be knowing when and how to free the
   > space that the operator must dynamically allocate in order to generate a
   > result.
   > 
   > I hope this makes sense.  Thanks in advance for your help.  Please e-mail
   > any responses.
Hope this helps.

Michael

   I think this is a good question whose answer should be posted rather than
   just emailed.

PS: this is another thing to just not do:
   
   One thing you can do is have operator+ have a static local object of class
   M that it puts the result into.  You can return a reference to this, and
   after you do the copy
   
   a = b + c
   
   to a from the temp the temp is not referenced anymore, so it is free to be
   used again whenever it is needed.  You don't have to use new or delete,
   and in fact the storage for the temp never has to get thrown away.

This makes the program non-reentrant.  Your solution is analogous to
the way that the AT&T C compiler does structure returns.  This has
long been considered one of the great implementation botches of that
compiler.
   
   Bob Hearn
   hearn@claris.com
   
Michael

hearn@claris.com (Bob Hearn) (04/05/89)

In article <8904042239.AA07890@yahi.stanford.edu> tiemann@lurch.stanford.edu writes:
>Well, it all depends on the compiler you are using.  I know that at
>least AT&T cfront and GNU C++ are smarter than this.  What happens in
>these compilers is that the caller passes the address of the place
>where the new temporary should be initialized.  Depending on the way
>it is initialized, there may be no overhead visible from the call to
>operator+ at all:
>
>M operator+(M x, M y)
>{
>  return M (x.value () + y.value ());
>}
>
>In general, if the compiler can get away with generating an invisible
>temporary, it can do quite clever things.
>

Are you saying the compiler recognizes the fact that there's an M being
constructed in the return statement, and uses an already allocated temporary
instead of making a new one on the stack?  That's some compiler.  What
would really be nice is to have some way of using an existing object as
the result.  Of course, you can have  Madd(M &x, M &y, M &result), but then
you can't use operator overloading :-(.
I disagree that this is a case in which one should leave the efficiency
questions to the compiler.  In general one can assume, as Stroustrup
does (p. 178), that a situation like this is going to require putting the
object on the stack.  There are cases where this is simply too expensive;
I guess there is no option then but to do something like Madd(M &x, M &y,
M &result) and lose operator overloading.
Stroustrup points out in the same paragraph, if I had bothered to look
before, that what I described previously (returning a reference to a static
object) is not a good idea.

Bob Hearn
hearn@claris.com