Topic: Named Return Value Optimisation- a good thing?


Author: phalpern@truffle.ma.ultranet.com (Pablo Halpern)
Date: 1997/05/19
Raw View
bparker@gil.com.au (Brian Parker) wrote:

>phalpern@truffle.ma.ultranet.com (Pablo Halpern) wrote:
>
>>Show me an example of code with correct copy semantics that breaks with
>>the application of the NRV rule. I don't think there is such an example.
>
>OK, I'll give this one last try (I promise).
> ...
>
>In this example, assume that Array is a standard concrete array class
>with correct copy (value) semantics. Assume that SubArray is a view on
>an Array with reference semantics. Further assume that Array and
>SubArray are reference counted so that any shared memory is
>automatically released when the last view on it is gone.

The way you have it implemented, a SubArray is *not* really a view on an
Array, but rather a view on the internal structure of an Array. If it
were a view on an Array itself, then the SubArrays would become invalid
when the Array was destroyed, which is not the case. In fact the Array
itself is really just a view on its own internal data structure, thus
the Array has half-reference, half-value semantics, which always causes
problems in the presents of RVO.

> ...
>
>Array NotAContrivedFunction(SubArray& sub1, SubArray& sub2)
>{
> Array temp(100,100); // we are not interested in the shared array
>    // -just the SubArrays so use a local Array
>    // (initialised to 0)
>
> sub1 = temp.GetSharedView(0,0,50,50); // sub1 now views from
>      //  (0,0) to (50,50)
>
> sub2 = temp.GetSharedView(30,30,100,100); // sub2 now views from
>      //  (30,30) to (100,100)
>
> return temp; // now return a *copy* of temp
>}
>
>int main( )
>{
> SubArray s1, s2;
> Array acopy = NotAContrivedFunction(s1, s2);
>
> // modify subarray views
> s1(0,0) = 10;
>
> cout << acopy(0,0) << endl;
> // should output 0 but with NRVO acopy shares storage with the
> // SubArrays and hence is modified and 10 is output
>}
>
>How's that example?

Much closer to reality, but still not something I would condone. RVO
does break down in circumstances where a reference is intended to remain
valid even after the original object goes out of scope. This style of
programming is common in languages like LISP but much less common in
C++. In lisp, value copies are only made explicitly; all other copies
are reference copies. In C++, we can make a class have either reference
or value semantics. If we use reference semantics consistently, then we
most often have a non-operator function to perform deap-copies. The
compiler will never elide an explicit function call, so if we have a
clean design, we don't have to worry about our copy operations being
incorrectly elided. One very valuable thing that Brian points out is
that when we teach people about clean class design and the "big 4"
functions, we must also teach them about clean aliasing design. Martin D
Kealey gave a good description of how to fix this particular aliasing
problem.

Even if someone eventually comes up with a proper counter-example, I
still believe that RVO should not be a compiler-specific "unsafe" level
of optimizats intende
-------------------------------------------------------------
Pablo Halpern                   phalpern@truffle.ultranet.com

I am self-employed. Therefore, my opinions *do* represent
those of my employer.
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/05/20
Raw View
phalpern@truffle.ma.ultranet.com (Pablo Halpern) wrote:

>...
>The way you have it implemented, a SubArray is *not* really a view on an
>Array, but rather a view on the internal structure of an Array. If it
>were a view on an Array itself, then the SubArrays would become invalid
>when the Array was destroyed, which is not the case. In fact the Array
>itself is really just a view on its own internal data structure, thus
>the Array has half-reference, half-value semantics, which always causes
>problems in the presents of RVO.

Yes, others have also made similar assessments of this example.

>
>>How's that example?

>Much closer to reality, but still not something I would condone. RVO
>does break down in circumstances where a reference is intended to remain
>valid even after the original object goes out of scope. This style of
>programming is common in languages like LISP but much less common in
>C++. In lisp, value copies are only made explicitly; all other copies
>are reference copies. In C++, we can make a class have either reference
>or value semantics. If we use reference semantics consistently, then we
>most often have a non-operator function to perform deap-copies. The
>compiler will never elide an explicit function call, so if we have a
>clean design, we don't have to worry about our copy operations being
>incorrectly elided. One very valuable thing that Brian points out is
>that when we teach people about clean class design and the "big 4"
>functions, we must also teach them about clean aliasing design. Martin D
>Kealey gave a good description of how to fix this particular aliasing
>problem.

>Even if someone eventually comes up with a proper counter-example, I
>still believe that RVO should not be a compiler-specific "unsafe" level
>of optimizats intende
>-------------------------------------------------------------
>Pablo Halpern                   phalpern@truffle.ultranet.com

I agree with your assessment of the example, but to be honest I am
still uncomfortable with the idea of a draft-sanctioned "optimisation"
breaking even badly designed code. The standard doesn't (and couldn't)
define what it means for a class to implement "proper" value semantics
or "proper" reference semantics, and if it doesn't then I would expect
my, otherwise legal, example to be deterministic.

That said, I agree that in practical terms the NRVO wouldn't cause
problems, and I wouldn't have any qualms about an explicit paragraph
in the draft that bent the language enough just to allow this
optimisation. As I've stated before, my main concern is with the
extension of [class.copy] paragraph 15 to also allow copies of
function arguments to be elided (which definitely can break even code
that uses classes with proper value and reference semantics).

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/05/14
Raw View
Martin D Kealey <martin@kcbbs.gen.nz> wrote:
>>...
>> the fact that garbage collection has been added
>> to avoid the risk of dangling references doesn't mean that the design of
>> Array should be changed to give it reference semantics. I still think it is
>> a reasonable design (if not a great one).

>A "reasonable design" would use 3 public classes, not two.

>There are two ways to do this:
>(a) split "Array" into value and reference classes, and work with the
>    reference classes most of the time (thus making "copy" an explicit
>    user-callable function); or
>(b) have Array::operator()(int,int) return a proxy to enforce copy-on-
>    write.

>Either of these would create a set of classes that fullfilled the
>expectations of the NRV transformation (but I suspect that (b) would be
>more to your liking).

>-Martin.

Ok, I see your point, so let me downgrade my claim to-
"I think it is *a* design (if not a reasonable one)".

By that I mean that the example was (originally) intended to show that
the problems with [class.copy] paragraph 15's flawed definition of
aliasing between objects can also manifest itself in the context of
the NRVO (see the thread "Subtle bug in [class.copy]?"), but as this
thread has shown, finding counter-examples that are also plausible and
well designed may well be impossible in this case.

Thanks,
Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/05/15
Raw View
anhaeupl@late.e-technik.uni-erlangen.de \d (Bernd Anh\dupl) wrote:
>>...
>>   Bear in mind that for the vast bulk of code that works correctly with the
>>   NRVO, it makes no difference whether the standard mentions it or not. It is
>>   only in the problem cases that it becomes important, and it seems to me that
>>   for these cases, making special allowances in the standard for the NRVO
>>   effectively states that it's the programmer's problem, whereas not
>>   mentioning it forces compiler-writers to provide a means to disable it and
>>   to document that it may lead to semantic changes (i.e. is non-standard
>>   conforming). I prefer the second alternative.
>>   ,Brian Parker (bparker@gil.com.au)

>And, since RVO seems to be rather valuable to me, I prefer the first
>alternative. This paragraph 15 of class.copy simply emphasizes, that
>copy constructors must not do anything beside duplicating an object;
>especially the two copies   m u s t  obey the same semantics.

Yes,  the main additional semantics added by paragraph 15 is that a
copy constructor may in fact be called 0 or more times- without this
paragraph the draft guarantees (I think) that an explicit copy
constructor will be called 1 or more times; the fact that side effects
in copy constructors may be elided, however, has never been my
concern. The flaw in paragraph 15 is that it uses a fixed definition
of aliasing between objects that is wrong for many classes (see my
posts in the thread "Subtle bug in [class.copy]?" for further
details), which is a quite different problem. This problem mainly
occurs when copies are elided on function calls, but what I was
attempting to show in this thread is that it can, in theory, also
occur in the NRVO.

I have since realised, though, that this example doesn't demonstrate
what I wanted to show. I have posted a  clearer example in my response
to Pablo Halpern, although the general consensus is that even that
example is not well designed and is of no practical consequence.

>...
>For the copy assignment operator on the other hand the argumentation
>has to be different, since that operator at least does not get called
>unpredictably at the compilers choice if it is not also coded. I personally
>would indeed like that this paragraph 15 would be restricted to copy
>construction and especially exclude copy assignment, so that the programmer
>could define the exact assignment semantics for classes. This is not
>possible at the moment, since we do not know, whether the assignment
>operator even gets called.
>--
>Bernd Anhaeupl   Tel.:  +49 9131 857787
>LATE - Uni Erlangen
>Cauerstr. 7   Email: anhaeupl@late.e-technik.uni-erlangen.de
>91058 Erlangen
>---

That's an excellent point. Whilst I was aware that paragraph 15 could
in theory be used to elide an assignment, I couldn't think of any
example where a compiler could actually do so, due to the risk of
aliasing, but I suppose that a sufficiently smart compiler could do
flow analysis and even elide an assignment in this case.

One possible fix for paragraph 15 being discussed in the thread
"Subtle bug in [class.copy]?" is to explicitly limit it to the NRVO
which would also limit it to copy construction.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/05/10
Raw View
phalpern@truffle.ma.ultranet.com (Pablo Halpern) wrote:
>...
>The NRV rule works for classes that have interfaces with well-defined
>copy semantics. Copying the file handle without dup'ing it above causes
>your function to violate the copy semantics of the class. file is not a
>proper copy of f. If the NRV rule breaks this code, I have no sympathy.

>Show me an example of code with correct copy semantics that breaks with
>the application of the NRV rule. I don't think there is such an example.

>-------------------------------------------------------------
>Pablo Halpern                   phalpern@truffle.ultranet.com

OK, I'll give this one last try (I promise).

My last matrix example was panned by the critics because the matrix
also acted as a submatrix.

In this example, assume that Array is a standard concrete array class
with correct copy (value) semantics. Assume that SubArray is a view on
an Array with reference semantics. Further assume that Array and
SubArray are reference counted so that any shared memory is
automatically released when the last view on it is gone.

Now we need a function that will return two overlapped SubArrays which
are views onto the same Array. We are not interested in the actual
shared Array, just the subarrays- but we do want to return a *copy* of
the original array for other purposes. Got that? (I assure that this
is not contrived- I use a function like this all the time.)

Array NotAContrivedFunction(SubArray& sub1, SubArray& sub2)
{
 Array temp(100,100); // we are not interested in the shared array
    // -just the SubArrays so use a local Array
    // (initialised to 0)

 sub1 = temp.GetSharedView(0,0,50,50); // sub1 now views from
      //  (0,0) to (50,50)

 sub2 = temp.GetSharedView(30,30,100,100); // sub2 now views from
      //  (30,30) to (100,100)

 return temp; // now return a *copy* of temp
}

int main( )
{
 SubArray s1, s2;
 Array acopy = NotAContrivedFunction(s1, s2);

 // modify subarray views
 s1(0,0) = 10;

 cout << acopy(0,0) << endl;
 // should output 0 but with NRVO acopy shares storage with the
 // SubArrays and hence is modified and 10 is output
}

How's that example?

Also note that this problem is in theory not restricted to the *named*
return value optimisation, but also the return value optimisation
(i.e. where an unnamed temporary is returned directly) but that case
is even more difficult to abuse.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/05/10
Raw View
"Paul D. DeRocco" <strip_these_words_pderocco@ix.netcom.com> wrote:

>Brian Parker wrote:
>>
>> The passed-in reference parameter "file" which
>> is uninitialised just copies f's file handle as it knows f will be destroyed
>> on exit from the function.

>This is where the problem arises. It only _thinks_ it knows that f will
>be destroyed. If everything is done with strict copy semantics, meaning
>that a new file handle is allocated every time it copies, then it will
>all work. The optimization simply doesn't let you get as cute as you
>thought you could get.

>This is certainly something to watch out for, but it doesn't cause
>problems unless you're trying to do some optimization yourself.

>--

>Ciao,
>Paul

Yes, there are two distinct issues here, and I fear I may have
confused them myself in my first two examples.

The first issue (which is *not* the one I was concerned with) is
whether a compiler should ever be allowed to elide an explicitly
written constructor call. Clearly, the draft standard (and even the
ARM) intends for the compiler to be allowed to assume that the special
functions (constructors etc.) have been written to do only their
assigned task and nothing else, and so is free to elide them where it
can, ignoring any additional side-effects in them. That is one
reasonable approach for the language standard to take, and, as I said,
is not the issue I was addressing.

The second issue, which is the one (I thought) I was pointing out, is
that, given that a compiler is allowed to elide copies, when is it
actually safe to do so without changing the semantics of the code, and
here it is not so clear cut, and in particular it is not safe simply
when "the original object or the copy will never be used again" as
[class.copy] paragraph 15 incorrectly asserts- one also needs to
consider indirectly shared resources. This paragraph is particularly
dangerous when used to elide a copy of a parameter on a function call
and it is easy to find problem examples in this case. It is more
difficult to find examples for the case where a copy in a function
return is elided- I think my third example (see the Array class in my
response to Pablo Halpern) is a better example in this context.

The problems with paragraph 15 are certainly much less common in the
case of the NRVO, and it requires extremely contrived examples to
demonstrate it.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Jason Merrill <jason@cygnus.com>
Date: 1997/05/11
Raw View
> #ifdef MEYERS
>  return C(lhs) += rhs;
> #endif

Hmm...for the compiler to do RVO on this example, it must analyze
operator+= to make sure that it returns its first operand.  This is tricky
for an operator+= that might be defined in another translation unit; much
more complex than noticing that all returns return the same local variable.
On the other hand, the simple case of an op+= defined earlier in the same
TU is probably easier to implement in g++...

Of course, we all know that op+= SHOULD return its first operand, but it's
not a requirement.

Jason
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1997/05/07
Raw View
Brian Parker <bparker@gil.com.au> wrote:
: Oleg Zabluda wrote:

: >Brian Parker <bparker@gil.com.au> wrote:
: >: I would prefer to see
: >: that paragraph deleted and the NRV optimisation left outside the standard
: >: and require an explicit compiler switch to activate it where safe (which
: >: would be in most, but not all, codes); it seems to me that that gives the
: >: best of all worlds.

: >I think it would be absolutely unacceptable. It's actualy the worst
: >of all worlds. Both unpredictable performance and no standard way
: >of determining determine where NRVO is prohibited.
: >Oleg.

: But worse would be if the NRV optimisation was allowed indiscriminantly by
: the standard and there was no standard way to disable it if was giving
: incorrect results. I would be tempted to agree with you if compilers could
: be relied upon not to incorrectly apply the optimisation where there is
: resource aliasing between the object which is having its copy elided and
: some other object used later.

I agree that people should think more on exact definition when
RVO is prohibited. But even if correcting the definition proves to
be impossible, I would still prefer RVO be present by default,
and programmers just have to be aware of the issue. After all,
there are tons of other aliasing problems in C/C++ and they
don't seem to scare anyone.

RVO optimization just offers too much performance advantages, which
can't be gained without it.

Oleg.
--
Life is a sexually transmitted, 100% lethal disease.
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1997/05/07
Raw View
John E. Potter <jpotter@falcon.lhup.edu> wrote:
: g++ optimizes neither. [RVO nor NRVO - Oleg.]

g++ definitely performs RVO when called with -O option. g++ has problems
doing it in complex (hard) cases though, but in simple ones, like

inline Rational operator * (const Rational& a, const Rational& b)
{ return Rational( a.num() * b.num(), a.den() * b.den() ); }

it will not only perform RVO, but inline the whole thing as well.

Oleg.
--
Life is a sexually transmitted, 100% lethal disease.
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/05/08
Raw View
On 07 May 97 11:32:29 GMT, David Vandervoorde wrote:

: John E. Potter wrote:
: [...]
:  > This might be implemented as:
:  >         void add (void* RawMemoryForC, C const& lhs, C const& rhs) {
:  >                 C temp(lhs);
clean nothing
:  >                 temp += rhs;
clean temp
:  >                 new (RawMemoryForC) C(temp);
clean temp
:  >                 }
:  >
:  > And the RVO gives:
:  >         void add (void* RawMemoryForC, C const& lhs, C const& rhs) {
:  >                 new (RawMemoryForC) C(lhs);
clean nothing
:  >                 *(C*)RawMemoryForC += rhs;
clean raw
:  >                 }

: Well, except the compiler must still clean up
: *(C*)RawMemoryForC if an exception is thrown prior to the
: return-operation.

I don't see any problem.  Somehow, I think I missed your point?

John
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/05/08
Raw View
On 04 May 1997 17:24:11 PDT, Brian Parker wrote:

: As a related discussion to my previous post discussing what I perceive
: to be flaws in [class.copy] paragraph 15 (see "Subtle bug in CD2
: [class.copy]?"

I have seen that and convinced myself that those optimizations are not
all that easy and are not likely to be a problem; however, your
concerns are valid.  One of my compilers has optimization levels which
it warns may change the semantics of a program.  I expect that the
things which you talk about there will fall into that category.

: ), I was wondering what peoples' views were on the named
: return value (NRV) optimisation.

Here, my feelings are totally different.  The RVO is based on the
following:

   C f (...) {
      C temp(whoCares);
      // lots of stuff with no returns (or return temp if you must)
      return temp;
      }

The return is:

   C unnamedReturnValuePassedByCaller(temp);  // copy construct

and the function may be rewritten replacing all occurances of temp
with unnamedReturnValuePassedByCaller.

All that changes is one copy construction and one destructor.  If a
copy constructor constructs a copy, there is no difference other than
side effects of the two functions which the draft says may not be
counted upon.  IMO, any class which does not work under these
conditions is already broken.  So, where is your class broken?  Lets
see.

: // (Somewhat contrived)

[ unquestioned stuff removed in lots of places ]

:  // default constructor
:  File() {
:   file_handle = 0;
:   fOwner = false;
:  }

OK, now we know what a default looks like.

 // assignment operator, I assume would look like this.
 File& operator= (File const& rhs) {
  if (fOwner) _close(file_handle);
  file_handle = _dup(f.file_handle);
  fOwner = true;
  }

:  // get and assume ownership of file_handle (for calling legacy code)
:  int GetFileHandle() {fOwner = false; return file_handle;}

The comment says it!  This should become a default (null) object since
it has released its handle.  This is a destructor.

   { int temp(file_handle); file_handle = 0;
     fOwner = false; return temp; }

: // Create two *independent* file handle wrappers for file_name
: // Return one via the reference parameter "file" and just return the
: other.

And of course violate all encapsulation to make the point.

: File GetTwoFileHandles(File& file, const char * file_name)
: {
:  File f(file_name); // create a temporary file wrapper
:
:  file.file_handle = f.GetFileHandle();
:  file.fOwner = true;

This should have been
    file = f;
But that would not screw things up.  What if file already had a
file_handle?  Is that called a resource leak?

:  // conveniently, assume ownership of f's file_handle.
:  // This is safe because we are returning a separate copy by value.
:  // (Note that a compiler can't know that a file handle is being
:  // shared here- it looks just like an int being returned by value).

:  return f; // return a copy of f

With a correct GetFileHandle, this would return junk.

: }

What would happen with this?

   File f1, f2;
   File f3(f1);
   f2 = f1;

Should the copy ctor and assignment operator throw if they are asked
to copy an unowned handle?

Yes, you can write destructors that do not destroy, assignment
operators that do not assign, copy constructors which do not copy and
lots of other nasty stuff; however, RVO is not the problem.

I do not believe that a class with correct semantics for the big four
member functions (plus initialize/finalize if present) could ever be
messed up by RVO, but I am willing to listen.

John
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/05/08
Raw View
On 07 May 1997 13:02:33 PDT, Oleg Zabluda wrote:

: John E. Potter <jpotter@falcon.lhup.edu> wrote:
: : g++ optimizes neither. [RVO nor NRVO - Oleg.]

: g++ definitely performs RVO when called with -O option. g++ has problems
: doing it in complex (hard) cases though, but in simple ones, like

: inline Rational operator * (const Rational& a, const Rational& b)
: { return Rational( a.num() * b.num(), a.den() * b.den() ); }

: it will not only perform RVO, but inline the whole thing as well.

I think that we need to get our terms streight.  The above example has
nothing to do with the return value optimization, it just happens to
be in a return statement.  Since return uses the same rules as the =
form of construction, your example is no different from

 Rational r = Rational( a.num() * b.num(), a.den() * b.den() );

which without optimization becomes

 Rational r( a.num() * b.num(), a.den() * b.den() );

Did I get that right?  If not, it is still a trivial optimization of
an expression which constructs a temporary to be copied to another
object which is supported by almost all compilers.  To get the RVO, we
need a real temporary or local variable which has something done to it
prior to the return copy.  Using the return temporary in its place is
the RVO.  GNU has its extension to explicitely request this.  I have
heard that it will have the RVO in release 2.8 and that the extension
will eventually go away.  The following code tests for the RVO or the
GNU NRVO with a simple class.  It should be obvious that there are
three objects which must be destructed.  Without RVO there is another.
Results follow:

#include <iostream.h>
class C {
 friend ostream& operator << (ostream& os, C const& c) {
   return os << c.val; }
 public :
  C (int v) : val(v) { }
  C (C const& src) : val(src.val) { }
  ~C () { cout << " ~C\n"; }
  C& operator= (C const& rhs) { val = rhs.val; return *this; }
  C& operator+= (C const& rhs) { val += rhs.val; return *this; }
 private :
  int val;
 };
C operator+ (C const& lhs, C const& rhs)
#ifdef GNUNRVO
 return temp(lhs) // gnu
#endif
 {
#ifdef GNUNRVO
 temp += rhs;
#endif
#ifdef LIPPMAN
 C temp(lhs);
 temp += rhs;
 return temp;
#endif
#ifdef MEYERS
 return C(lhs) += rhs;
#endif
 }
int main () {
 C c1(3), c2(5);
 cout << c1 + c2 << '\n';
 return 0;
 }

Number of destructions:

   GNUNRVO LIPPMAN MEYERS
Borland   -  4  4
g++    3  4  4
USL cfront  -  3  4
IBM xlC   -  4  4

Optimization level made no difference.  So g++ has an extension which
accomplishes the same thing that ancient USL cfront did automatically.
There is no reason that Meyers' version could not also be optimized,
it just isn't by these compilers.  This is what Brian wants to make
illegal.  I dissagree with him, but would not like people to think
that RVO is less than it is.  It can remove a named local variable in
Lippman's case and a temporary which gets modified in Meyers' case.
The number of posts trying to accomplish this via some trickery or
some extension to the language easily outweigh this one objection.

John
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: David Vandevoorde <daveed@vandevoorde.com>
Date: 1997/05/08
Raw View
John Potter wrote:
[...]
> :  >   void add (void* RawMemoryForC, C const& lhs, C const& rhs) {
> :  >             new (RawMemoryForC) C(lhs);
> clean nothing
> :  >                 *(C*)RawMemoryForC += rhs;
> clean raw
> :  >                 }
>
> : Well, except the compiler must still clean up
> : *(C*)RawMemoryForC if an exception is thrown prior to the
> : return-operation.
>
> I don't see any problem.  Somehow, I think I missed your point?

Suppose the += operation throws an exception: then, since
the original object was an automatic variable, it would be
cleaned up (destructed) as you unwind the stack.

However, in your transcription, you no longer have that
automatic object, and so it doesn't get cleaned up (the
production of a placement new operation does not get
destructed when the stack is unwound).

 Daveed
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Brian Parker <bparker@gil.com.au>
Date: 1997/05/08
Raw View
John E. Potter wrote:
>...
>I must agree with another poster that it would be the worst of all worlds
>in being unspecified.  As it is, it is a quality of implementation issue
>and the marketplace will decide whether it is desirable.  Being allowed
>by the standard, will force those wishing to write portable code, to
>write in such a way as to work with or without it.  IMHO, a good idea.

But not adding specific allowances to the draft for the NRVO does not
*disallow* it- it just disallows its automatic application by the compiler
in situations that it can't prove as being safe, and hence will require
explicit confirmation by the programmer in those cases.

>...
>MO:  I think that most users would like to see as many temporaries as
>possible removed, and the market will demand RVO.  I have seen several
>classes lately which try to do different things depending upon which
>constructor is used.  I think that Scott also said that the job of a
>constructor is to construct an object.  I think that doing things like
>copy on write should include another layer of abstraction and there
>are good published ways to do it.  If the draft is approved as is, we
>will all learn to design so that things will work whether copies are
>made or not.  By C++ 2003, the issue will have been forgotten.
>John

I think that not giving the NRVO any special allowances in the draft would
be more likely to lead to programmer's modifying their performance-critical
code to be compatible with the optimisation (e.g. avoiding side-effects in
copy constructors) as the compiler won't be allowed to ignore such side-effects.

But the problem is not just with copy constructors doing inappropriate
things- see my thread "Subtle bug in [class.copy]?" for a fuller discussion
of the problems with aliasing that this clause introduces.

Let me make it clear that I am not anti-NRVO; I have a matrix package that
would benefit from it and I hope that Visual C++ provides it as an option in
the future. In fact my main gripe is with [class.copy] paragraph 15 that
goes way beyond allowing a copy to be elided on a function return- it
effectively destroys the guarantee of pass-by-value's not modifying its
argument and that really gives me the heebie-jeebies.

Although the same problems as I discussed in the thread "Subtle bug in
[class.copy]?" do apply in the case of the NRV optimisation, they are much
less likely to occur, in fact one has to go out of their way to contrive
buggy examples. Because it is such a widely applicable and useful
optimisation, I would expect compiler writers to provide it regardless- as
you say, it is a quality of implementation issue.

But the question is what to do with these uncommon buggy cases. The idea of
adding an escape clause for compiler writers deep within the bowels of the
draft standard isn't of much practical use to a programmer unfamiliar with
the named return value optimisation who gets stung by a subtle bug in his
release build. By adding special clauses for the NRVO in the standard, the
committee is forcing everyone who wishes to write bug-free code to be
cognizant of the mechanism of the NRVO or, equivalently, of the clause
allowing it (or more realistically, forcing them to become aware of it after
being stung by a bug caused by it).

In contrast, if the standard makes no special allowances for the
optimisation, then that doesn't *disallow* the optimisation- it can still be
provided and will remain applicable in the vast majority of cases, it's just
that someone writing performance-critical code will be forced to understand
the ramifications before explicitly activating a (non-standard) compiler
switch. (Note that a sufficiently clever compiler could still perform the
optimisation automatically in some cases by the "as if" rule.)

Someone who is aware of the NRVO would leave the optimisation enabled in
their release builds by default and, where necessary, write their code to be
safe under the optimisation (and comment it as such).

This still seems preferable to me; I don't think that an optimisation needs
be automatically applicable by a compiler in all cases  to be useful- a
potentially unsafe optimisation that can be applied to the vast majority of
code is equally useful. The "assume no aliasing" optimisation  is an example
of a similar (although less useful) optional potentially unsafe optimisation
supplied by compiler writers.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: phalpern@truffle.ma.ultranet.com (Pablo Halpern)
Date: 1997/05/08
Raw View
bparker@gil.com.au (Brian Parker) wrote:

>
> // get and assume ownership of file_handle (for calling legacy code)
> int GetFileHandle() {fOwner = false; return file_handle;}

Your class design would be more correct (see below for discussion of
reference vs. copy semantics) if this function were changed to:

        int GetFileHandle()
        {
          if (fOwner)
            return file_handle;
          else
            return dup(file_handle);
          fOwner = false;
        }

> ...

>// Create two *independent* file handle wrappers for file_name
>// Return one via the reference parameter "file" and just return the
>other.
>File GetTwoFileHandles(File& file, const char * file_name)
>{
> File f(file_name); // create a temporary file wrapper
>
> file.file_handle = f.GetFileHandle();
> file.fOwner = true;
>
> // conveniently, assume ownership of f's file_handle.
> // This is safe because we are returning a separate copy by value.
> // (Note that a compiler can't know that a file handle is being
> // shared here- it looks just like an int being returned by value).

NOT SO FAST! Your class is poorly designed. Either a file handle is a
shared resource and follows reference semantics (with an owner), or it
is not a shared resource and follows copy semantics. The two statements
above copy a File object without using the assignment operator (which
presumably would dup() the file handle).  You are using reference
semantics on a class that was designed for copy semantics.  The concept
of ownership and your use of dup() are not compatable.

The NRV rule works for classes that have interfaces with well-defined
copy semantics. Copying the file handle without dup'ing it above causes
your function to violate the copy semantics of the class. file is not a
proper copy of f. If the NRV rule breaks this code, I have no sympathy.

Show me an example of code with correct copy semantics that breaks with
the application of the NRV rule. I don't think there is such an example.

-------------------------------------------------------------
Pablo Halpern                   phalpern@truffle.ultranet.com

I am self-employed. Therefore, my opinions *do* represent
those of my employer.
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/05/08
Raw View
On 08 May 97 04:48:44 GMT, Briain Parker wrote:
: John E. Potter wrote:
: >...
: >I must agree with another poster that it would be the worst of all worlds
: >in being unspecified.  As it is, it is a quality of implementation issue
: >and the marketplace will decide whether it is desirable.  Being allowed
: >by the standard, will force those wishing to write portable code, to
: >write in such a way as to work with or without it.  IMHO, a good idea.

: But not adding specific allowances to the draft for the NRVO does not
: *disallow* it- it just disallows its automatic application by the compiler
: in situations that it can't prove as being safe, and hence will require
: explicit confirmation by the programmer in those cases.

 [ snip discussion of wording to stick to RVO ]

Since RVO can be detected by a conforming program, either the standard
allows the RVO via specific allowance to violate the "as if" rule or
RVO must be an extension.  To paraphrase Grace Hopper, never use the
bells and whistles provided by your vendor, only use the standard
features of the language.  Portable programs either get the RVO
because it is standard or they do not get it.  The current wording in
the draft allows the implementation to assume without proof that
 C c1(possiblyInitialized);
    stuff(c1);
    C c2(c1);
    c1.~C();
is identical to
    C c2(possiblyInitialized);
    stuff(c2);
in the special case where c1 is a local variable in a function and c2
is the value returned by the function.  No proof is required because
c1 is clearly going out of scope and can not possibly be used again.
The message to the user is that the assumption better be valid in
their classes.  I think that message can also be found in the writings
of Cargill, Meyers, Murray, Stroustrup, ...

The current wording in the draft allows RVO in all cases of a function
which always returns the same thing, which I think is a good thing.
Since my functions are always SE/SE, there is only one return and it
is trivial to show that RVO is valid.  The other possible problems
with that wording and the proofs needed is in the other thread and I
would like to keep it that way.

In another post, you said:
: The fact remains that in the code I posted,
: File f2 = GetTwoFileHandles(f1, "test.txt");
: sets f2 to a completely different value than
: File f2;
: f2 = GetTwoFileHandles(f1, "test.txt");
: when the NRV optimisation is applied (assuming that an assignment operator
: equivalent to the copy constructor was defined- unfortunately I left that
: out of the posted example).

It may be worth noting that two optimizations were required to show
that the class was broken.  Without any optimization,
    File f2 = GetTwoFileHandles(f1, "test.txt");
is
 File unnamedTemp(GetTwoFileHandles(f1, "test.txt"));
 File f2(unnamedTemp);
    destruct and dispose of unnamedTemp;
GetTwoFileHandles constructs a local and invalidates it.  The broken
copy ctor creates a valid (?) copy of the invalid object in
unnamedTemp.  This is then copied to f2.  If we allow RVO but not the
removal of unnamedTemp, unnamedTemp becomes the invalid object and the
broken copy ctor creates a valid (?) copy in f2.  If we remove RVO and
the unnamedTemp, the broken copy ctor creates a valid (?) copy of the
invalid local in f2.  If we allow RVO and remove unnamedTemp, f2
becomes the invalid object and there is no broken function to fix it.
In the assignment version with RVO, the unnamedTemp returned is the
invalid object and the broken operator= creates a valid (?) copy in
f2.

John
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/05/08
Raw View
On 08 May 97 04:48:06 GMT, daveed wrote:

: John Potter wrote:
: [...]
: > :  >   void add (void* RawMemoryForC, C const& lhs, C const& rhs) {
: > :  >             new (RawMemoryForC) C(lhs);
: > clean nothing
: > :  >                 *(C*)RawMemoryForC += rhs;
: > clean raw
: > :  >                 }
: >
: > : Well, except the compiler must still clean up
: > : *(C*)RawMemoryForC if an exception is thrown prior to the
: > : return-operation.
: >
: > I don't see any problem.  Somehow, I think I missed your point?

: Suppose the += operation throws an exception: then, since
: the original object was an automatic variable, it would be
: cleaned up (destructed) as you unwind the stack.

: However, in your transcription, you no longer have that
: automatic object, and so it doesn't get cleaned up (the
: production of a placement new operation does not get
: destructed when the stack is unwound).

Now I see.  I wasn't clear in my original post.  Return by value could
be implemented, by the implementation not me, and usually is by doing
roughly what I showed.  The RVO is then implemented by the
implementation and the responsibility for the destruction of the
constructed return value is assumed in place of that for temp which
has been removed.

John
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/05/09
Raw View
jpotter@falcon.lhup.edu (John Potter) wrote:

>Since RVO can be detected by a conforming program, either the standard
>allows the RVO via specific allowance to violate the "as if" rule or
>RVO must be an extension.  To paraphrase Grace Hopper, never use the
>bells and whistles provided by your vendor, only use the standard
>features of the language.  Portable programs either get the RVO
>because it is standard or they do not get it.

But an optimisation is not the same thing as a syntactic extension- it
is transparent at the source level. If [class.copy] paragraph 15 was
removed from the draft and NRVO was relegated to being a possibly
semantic-changing optimisation then what would be the practical
effect?

The codes that were correct in the presence of the NRVO (either by
design or fortuitously) will continue to be correct whether compiled
with or without the NRVO; removing paragraph 15 does not suddenly make
them non-draft conforming.

The only effect is on the uncommon, admittedly poorly designed cases
that are incorrect. In this case, with paragraph 15 present they are
defined as having non-deterministic behaviour and *must* be redesigned
to allow the NRVO even if speed is not an issue. If paragraph 15 is
not present then they have guaranteed deterministic behaviour, and
only if the NRVO  is enabled do they need to be redesigned to support
it.

So the upshot is that if paragraph 15 was not there, no codes would be
slower (provided the NRVO is explicitly activated) and a few
(uncommon) cases become correct.

But I agree with you (and with Pablo Halpern) that my examples were
contrived and that in practical terms any well-designed class will not
have a problem with the NRVO.

I suppose it is just a philosophical point of view, but I still think
that a properly specified language should give deterministic results
even for "badly-designed"  code.  The examples I posted, whilst
contrived, are simple code sequences that I would expect to be
provably wrong or right- not non-deterministic. The closest example I
can think of of similar non-determinism of explicitly written code in
the standard is in the non-specified order of evaluation of operands
of an expression, but at least in this case the non-determinism is
carefully delimited between sequence points (other
implementation-defined aspects of the language are of course
deterministic and specified for a given platform).

That said, I would not be particularly concerned if [class.copy]
paragraph 15 could be rewritten to be limited to the NRVO, just so
long as its dangerous extension to general paramater passing is
removed.

Brian Parker (bparker@gil.com.au)


---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Brian Parker <bparker@gil.com.au>
Date: 1997/05/10
Raw View
John Potter wrote:
>On 04 May 1997 17:24:11 PDT, Brian Parker wrote:
>
>: As a related discussion to my previous post discussing what I perceive
>: to be flaws in [class.copy] paragraph 15 (see "Subtle bug in CD2
>: [class.copy]?"
>
>I have seen that and convinced myself that those optimizations are not
>all that easy and are not likely to be a problem; however, your
>concerns are valid.  One of my compilers has optimization levels which
>it warns may change the semantics of a program.  I expect that the
>things which you talk about there will fall into that category.

And that's fine. I have no qualms about potentially dangerous optimisations
being allowed as non-standard extensions. In fact, I rely upon some all the
time in Visual C++ which at its highest optimisation level produces slightly
non-standard floating point arithmetic and non-standard-conforming inlined
intrinsic functions (the error number is not set correctly). These are
useful most times, but I know that I can always disable them in the few
cases where they are inappropriate for my code, and moreover by disabling
them I can formally prove critical parts of my code if required. If the
standard was modified to allow these kinds of sematic-changing optimisations
then the language would be effectively useless for critical code.

>: ), I was wondering what peoples' views were on the named
>: return value (NRV) optimisation.
>
>Here, my feelings are totally different.  The RVO is based on the
>following:
>
>   C f (...) {
>      C temp(whoCares);
>      // lots of stuff with no returns (or return temp if you must)
>      return temp;
>      }
>
>The return is:
>
>   C unnamedReturnValuePassedByCaller(temp);  // copy construct
>
>and the function may be rewritten replacing all occurances of temp
>with unnamedReturnValuePassedByCaller.
>
>All that changes is one copy construction and one destructor.  If a
>copy constructor constructs a copy, there is no difference other than
>side effects of the two functions which the draft says may not be
>counted upon.  IMO, any class which does not work under these
>conditions is already broken.  So, where is your class broken?  Lets
>see.
>

...useful discussion of flaws in File class cut...

>
>Yes, you can write destructors that do not destroy, assignment
>operators that do not assign, copy constructors which do not copy and
>lots of other nasty stuff; however, RVO is not the problem.
>
>I do not believe that a class with correct semantics for the big four
>member functions (plus initialize/finalize if present) could ever be
>messed up by RVO, but I am willing to listen.
>
>John

You are quite quite right that the File wrapper class example I gave was
completely contrived and in practice would never be written that way. I used
it primarily to demonstrate that the issue of resource aliasing was not
limited to dynamically allocated memory.

Here is a more plausible example. It is loosely based on an actual matrix
class (see http://www.gil.com.au/~bparker).
In the following, Matrix is a Matlab-like matrix class that can either
represent a primary 2-D array, or can be a submatrix (i.e. a reference to a
portion of another matrix)- an internal boolean flag distingushes the two uses.

The following statement initialises a Matrix sub as a submatrix view on
another Matrix A from index (0,0) to (10,10)-
Matrix sub;
sub.MakeSubMatrixView(A, Index(0,10), Index(0,10));

Copy constructing a Matrix from a submatrix will produce a deep copy of the
submatrix (i.e. Matrices have value semantics).

OK, now with that overview, the example follows.

We want a function that will return a copy of the top left corner (of shape
11 x 11) of an array (ignoring issues of const-correctness).

Matrix GetTopLeftCorner(Matrix& A)
{
        Matrix sub;
        sub.MakeSubMatrixView(A, Index(0,10), Index(0,10));
        // Get top-left corner, sub is a submatrix with reference sematics
to the
        // top left corner of A

        return sub;
        // return a copy of sub (the returned copy will be a primary matrix
NOT a submatrix)
}

int main()
{
        Matrix M(100,100) = 0.0; // create 100 x 100 matrix initialised to 0


        Matrix N = GetTopLeftCorner(M);
        // N should now be a primary matrix (i.e. not a submatrix) of shape
11 x 11, initialised to 0

        M(0,0) = 1.0;   // changing M shouldn't affect N

        cout << N(0,0);         // should output 0.0

        // BUT, if the NRV "optimisation" is allowed N will be silently
turned into a submatrix which is a view         // on Matrix B and 1.0 will
be output!
}

This is, I think, an entirely plausible, only marginally contrived example,
and Matrix is a properly designed value type with the "big four" members
correctly implemented.

I agree with you that the NRVO will cause no problems for the vast bulk of
code and it is a useful optimisation that should be provided (in contrast to
the deadly problems that the changes to parameter-passing semantics implied
by [class.copy] paragraph 15 will cause if it is left in the standard.)

But I think that if an optimisation even only fails in uncommmon cases, it
is not appropriate for a standard to change the sematics of a language as a
work-around for it.

Bear in mind that for the vast bulk of code that works correctly with the
NRVO, it makes no difference whether the standard mentions it or not. It is
only in the problem cases that it becomes important, and it seems to me that
for these cases, making special allowances in the standard for the NRVO
effectively states that it's the programmer's problem, whereas not
mentioning it forces compiler-writers to provide a means to disable it and
to document that it may lead to semantic changes (i.e. is non-standard
conforming). I prefer the second alternative.

,Brian Parker (bparker@gil.com.au)











---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: "Paul D. DeRocco" <strip_these_words_pderocco@ix.netcom.com>
Date: 1997/05/10
Raw View
Brian Parker wrote:
>
> The passed-in reference parameter "file" which
> is uninitialised just copies f's file handle as it knows f will be destroyed
> on exit from the function.

This is where the problem arises. It only _thinks_ it knows that f will
be destroyed. If everything is done with strict copy semantics, meaning
that a new file handle is allocated every time it copies, then it will
all work. The optimization simply doesn't let you get as cute as you
thought you could get.

This is certainly something to watch out for, but it doesn't cause
problems unless you're trying to do some optimization yourself.

--

Ciao,
Paul

(Please remove the "strip_these_words_" prefix from the return
address, which has been altered to foil junk mail senders.)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Brian Parker <bparker@gil.com.au>
Date: 1997/05/10
Raw View
At 03:40  8/05/97 -John E. Potter wrote:

>: Here is a more plausible example. It is loosely based on an actual matrix
>: class (see http://www.gil.com.au/~bparker/).
>: In the following, Matrix is a Matlab-like matrix class that can either
>: represent a primary 2-D array, or can be a submatrix (i.e. a reference to a
>: portion of another matrix)- an internal boolean flag distingushes the two
>: uses.
>
>This is what I call a schizophrenic class.  It can't make up its mind
>whether it is a value semantics matrix or a reference semantics
>submatrix.  And the fatal part is that it has a "copy" constructor
>which produces a personnality change rather than a copy.  Any class
>with a copy ctor or assignment operator which does not produce a
>copy which is a semantic duplicate of the parameter is broken.  As ark
>has been known to say, when this kind of thing is needed, another level
>of abstraction is always the answer.  In this case, a value Matrix and
>a reference Submatrix (proxy) class.
>
>We may not be able to agree on what valid big four functions must do.  A
>proxy class is not easy to write and due to the lack of operator.() it is
>sometimes impossible.  You may well have good reasons for doing what you
>are the way you are; however, I will likely continue to believe that it
>is the class that is broken not the NRVO.
>
>No matter how good NRVO is, it can easily be disabled by placing more
>than one return with different arguments in the function.  With the
>two implementations that I have seen it is even easier than that.  AFAICT
>they will only optimize the case where temp is declared at the top and
>is returned at the bottom.  In the posted sample, changing to
> return temp += rhs;
>killed it.  The future could bring better (or worse depending upon view)
>implementations.
>
>Not convinced, but always open minded,
>John

You are right that the matrix example is not a particularly good design. In
fact, my actual matrix class does use value semantics exclusively and
requires an explicit reference for reference semantics, which is why I refer
to the example as an "only marginally contrived example".

But I think that it is plausible in that a tyro may actually use this
design. Your suggestion that the NRVO can always be disabled illustrates my
argument that if the standard makes a special case for the NRVO then
everyone will need to intimately aware of its mechanism so that they can use
such workarounds. OTOH if you are already familiar with it then it would be
no problem for you to activate an optimisation switch.

The reason I am harping on about this issue (and I have gone on a bit
haven't I) is not so much the NRVO itself, but rather the fact that
[class.copy] paragraph 15 will definitely need rewriting as it goes way
beyond allowing the NRVO, but I am not confident that it can be rewritten
(at this late stage) to only allow the NRVO and nothing else, without
leaving other loopholes.
Given that, I think the simplest solution is to just delete that paragraph.
The point I have been making in my recent posts is that this would have no
practical detriment as the NRVO (which is the whole point of paragraph 15)
will still be provided by compiler-writers as a (possibly) semantic-changing
optimisation, but the basic semantics of the standard-described language
won't have been perverted. There seems to be the perception that if
paragraph 15 is deleted then the NRVO is completely banned, which I don't
think is correct.

,Brian Parker (bparker@gil.com.au)


---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Jason Merrill <jason@cygnus.com>
Date: 1997/05/10
Raw View
>>>>> John Potter <jpotter@falcon.lhup.edu> writes:

> On 07 May 1997 13:02:33 PDT, Oleg Zabluda wrote:

> : inline Rational operator * (const Rational& a, const Rational& b)
> : { return Rational( a.num() * b.num(), a.den() * b.den() ); }

As John says, this is not the RVO, but rather eliding of a temporary.  This
is an idea similar to the RVO, but has been in the language much longer.

> I have heard that [g++] will have the RVO in release 2.8

Nope.  I don't know when the RVO will be implemented.

Jason
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: jpotter@falcon.lhup.edu (John Potter)
Date: 1997/05/14
Raw View
On 10 May 97 05:36:22 GMT, Brian Parker wrote:

: phalpern@truffle.ma.ultranet.com (Pablo Halpern) wrote:
: >...
: >The NRV rule works for classes that have interfaces with well-defined
: >copy semantics. Copying the file handle without dup'ing it above causes
: >your function to violate the copy semantics of the class. file is not a
: >proper copy of f. If the NRV rule breaks this code, I have no sympathy.

: >Show me an example of code with correct copy semantics that breaks with
: >the application of the NRV rule. I don't think there is such an example.

: >-------------------------------------------------------------
: >Pablo Halpern                   phalpern@truffle.ultranet.com

: OK, I'll give this one last try (I promise).

And I will give one last rebutal (ditto).

: My last matrix example was panned by the critics because the matrix
: also acted as a submatrix.

: In this example, assume that Array is a standard concrete array class
: with correct copy (value) semantics. Assume that SubArray is a view on
: an Array with reference semantics. Further assume that Array and
: SubArray are reference counted so that any shared memory is
: automatically released when the last view on it is gone.

It has occured to me that there is a continuing thread through all of
the examples.  This one is much clearer.  What we have is a class plus
a proxy class used for reference semantics.  The only standard example
of a proxy class is vector<bool>/bvector (please no comments on which,
that has been done and we all disagree) and its reference class.  Each
of the examples in this thread used default constructed reference
classes.  In bvector, there is no default constructor and the
constructor which prohibits it is private and only available to
bvector.  This problem can be solved by design.

The second thing has been passing a reference class by reference to
some function.  References to references are prohibited for the built
in references; however, that is not true for proxy classes.  They are
not real references and the safty built into the language for real
references must be enforced by the programmer.

I think this covers the common (mis)design in all examples.

That still leaves open the copy constructor for a proxy class.  If it
is private, proxies may not be passed to or returned by functions by
value.  But by reference is wrong as above.  If it is not private, the
user may copy construct a proxy class variable from the return of a
member of the basic class using the proxy.  But reference variables
are allowed in the language; so, I guess that I will need to accept
proxy variables, just not default constructed ones.  The bvector
copy-ctor is public.

And, of course, this has nothing to do with NRVO since I am convinced
that it works on all reasonable classes.

John
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Martin D Kealey <martin@kcbbs.gen.nz>
Date: 1997/05/14
Raw View
In article <1.5.4.32.19970513125807.0068b0e4@mail.ipswich.gil.com.au>
 Brian Parker wrote:
> Martin D Kealey wrote:
>> Brian Parker wrote:
>>> Array NotAContrivedFunction(SubArray& sub1, SubArray& sub2)
>>> {
>>>     Array temp(100,100);    // we are not interested in the shared array
>>>                 // -just the SubArrays so use a local Array
>>>                 // (initialised to 0)
>>>     sub1 = temp.GetSharedView(0,0,50,50);
>>>     sub2 = temp.GetSharedView(30,30,100,100);
>>>     return temp;
>>> }
>> What exactly are sub1 and sub2 supposed to represent at this point?
>> The obvious answer to me is that they represent references to the
>> now-defunct temp object.

> Well yes, except that the storage of temp that they refer to is not defunct
> as Array is garbage-collected ...

I don't care *how* Array is implemented, I asked what does it
*represent*.  Does it represent a value, or a reference?

If Array represents a value, then "Array::GetSharedView()" should return
something that represents either
(1) a reference to an Array object; or
(2) a copy of a portion of an Array.

Currently it is neither -- logically it's a reference to an Array
r-value, which is not the same thing.  Non-const references to r-values
of built in types are banned for good reason ; user defined classes
should likewise ban them, since the compiler expects them to behave
like built-in types in certain respects.

Exitting NotAContrivedFunction leaves sub1 and sub2 as dangling
logical references to temp; the fact that their implementation is such
that they
don't delete a shared copy of temp's *value* doesn't change that.  Later
on you try an use that same value, but find that it doesn't look like the
value you thought it ought to.

> I don't know that I agree with you here. It is quite a normal design to have
> an Array with value semantics and a separate SubArray class with reference
> semantics onto that Array;

Yes, correct; but let's be clear here about what is implementation
detail, and what is being modelled by the class.

class SubArray is a reference to something.  Never mind
implementation details like reference counting or garbage collection:
what is the logical target of that reference?  What is the class that
represents that target? Either

(a) the logical target of the reference is class Array, in which case
    you have two logically dangling references when you run off then end
    of NotAContrivedFunction; or

(b) the target of the reference is not class Array but something else.
    Your problem is that SubArray and Array have different ideas about
    when that target needs copying, because Array regards it as "an
    implementation detail", while SubArray regards the sharing of that
    value as an apparent part of its functionality.

> the fact that garbage collection has been added
> to avoid the risk of dangling references doesn't mean that the design of
> Array should be changed to give it reference semantics. I still think it is
> a reasonable design (if not a great one).

A "reasonable design" would use 3 public classes, not two.

There are two ways to do this:
(a) split "Array" into value and reference classes, and work with the
    reference classes most of the time (thus making "copy" an explicit
    user-callable function); or
(b) have Array::operator()(int,int) return a proxy to enforce copy-on-
    write.

Either of these would create a set of classes that fullfilled the
expectations of the NRV transformation (but I suspect that (b) would be
more to your liking).

-Martin.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: anhaeupl@late.e-technik.uni-erlangen.de \d (Bernd Anh\dupl)
Date: 1997/05/14
Raw View
In article <1.5.4.32.19970508154738.00686dd0@mail.ipswich.gil.com.au> Brian Parker <bparker@gil.com.au> writes:

   Here is a more plausible example. It is loosely based on an actual matrix
   class (see http://www.gil.com.au/~bparker).
   In the following, Matrix is a Matlab-like matrix class that can either
   represent a primary 2-D array, or can be a submatrix (i.e. a reference to a
   portion of another matrix)- an internal boolean flag distingushes the two uses.

   The following statement initialises a Matrix sub as a submatrix view on
   another Matrix A from index (0,0) to (10,10)-
   Matrix sub;
   sub.MakeSubMatrixView(A, Index(0,10), Index(0,10));

   Copy constructing a Matrix from a submatrix will produce a deep copy of the
   submatrix (i.e. Matrices have value semantics).

So the copy constructor does not simply duplicate the object, but creates
another object with   d i f f e r e n t   semantics; i.e. writing to the
original (Sub)-Matrix references the elements of another matrix, whereas
assignment to the copied matrix does not do that.

   OK, now with that overview, the example follows.

   We want a function that will return a copy of the top left corner (of shape
   11 x 11) of an array (ignoring issues of const-correctness).

   Matrix GetTopLeftCorner(Matrix& A)
   {
    Matrix sub;
    sub.MakeSubMatrixView(A, Index(0,10), Index(0,10));
    // Get top-left corner, sub is a submatrix with reference sematics
   to the
    // top left corner of A

    return sub;
    // return a copy of sub (the returned copy will be a primary matrix
   NOT a submatrix)
   }

   int main()
   {
    Matrix M(100,100) = 0.0; // create 100 x 100 matrix initialised to 0


    Matrix N = GetTopLeftCorner(M);
    // N should now be a primary matrix (i.e. not a submatrix) of shape
   11 x 11, initialised to 0

    M(0,0) = 1.0;   // changing M shouldn't affect N

    cout << N(0,0);         // should output 0.0

    // BUT, if the NRV "optimisation" is allowed N will be silently
   turned into a submatrix which is a view         // on Matrix B and 1.0 will
   be output!
   }

   This is, I think, an entirely plausible, only marginally contrived example,
   and Matrix is a properly designed value type with the "big four" members
   correctly implemented.

As mentioned above, I would not call a copy constructor correct, that does
more than simply duplicating an object with both copies obeying the same
semantics.

   I agree with you that the NRVO will cause no problems for the vast bulk of
   code and it is a useful optimisation that should be provided (in contrast to
   the deadly problems that the changes to parameter-passing semantics implied
   by [class.copy] paragraph 15 will cause if it is left in the standard.)

   But I think that if an optimisation even only fails in uncommmon cases, it
   is not appropriate for a standard to change the sematics of a language as a
   work-around for it.

   Bear in mind that for the vast bulk of code that works correctly with the
   NRVO, it makes no difference whether the standard mentions it or not. It is
   only in the problem cases that it becomes important, and it seems to me that
   for these cases, making special allowances in the standard for the NRVO
   effectively states that it's the programmer's problem, whereas not
   mentioning it forces compiler-writers to provide a means to disable it and
   to document that it may lead to semantic changes (i.e. is non-standard
   conforming). I prefer the second alternative.

   ,Brian Parker (bparker@gil.com.au)

And, since RVO seems to be rather valuable to me, I prefer the first
alternative. This paragraph 15 of class.copy simply emphasizes, that
copy constructors must not do anything beside duplicating an object;
especially the two copies   m u s t  obey the same semantics.

By the way, you simply have to change your GetTopLeftCorner function
a little bit to work like you expected, you only must not create
a view, if you want to have a primary matrix.

   Matrix GetTopLeftCorner(Matrix& A)
   {
    Matrix sub;
    sub.MakeSubMatrix(A, Index(0,10), Index(0,10));
           // Get top-left corner not as an view of some other
           // matrix, but as primary matrix.

    return sub;
    // the copy constructor may now be eliminated without
    // changing the semantics.
   }

By the way, I also think, it is better to enforce the programmer
to explicitly code, what he wants (i.e. if you want a primary
matrix, code that and not a view to another matrix), than to
rely on subtile semantics coded especially into the copy constructor.
This is especially obvious, if we look e.g. into section decl.init.ref,
where we read, that rvalues (say e.g. function return values) passed via
reference to other functions may also be only the n-th copy of the
original rvalue with n being any non-negative number starting
with 0 at the compilers choice, i.e. you cannot know, wether a copy
constructor gets called or not.

For the copy assignment operator on the other hand the argumentation
has to be different, since that operator at least does not get called
unpredictably at the compilers choice if it is not also coded. I personally
would indeed like that this paragraph 15 would be restricted to copy
construction and especially exclude copy assignment, so that the programmer
could define the exact assignment semantics for classes. This is not
possible at the moment, since we do not know, whether the assignment
operator even gets called.




--
Bernd Anhaeupl   Tel.:  +49 9131 857787
LATE - Uni Erlangen
Cauerstr. 7   Email: anhaeupl@late.e-technik.uni-erlangen.de
91058 Erlangen
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/05/14
Raw View
jpotter@falcon.lhup.edu (John Potter) wrote:

>...
>: OK, I'll give this one last try (I promise).

>And I will give one last rebutal (ditto).

>: My last matrix example was panned by the critics because the matrix
>: also acted as a submatrix.

>: In this example, assume that Array is a standard concrete array class
>: with correct copy (value) semantics. Assume that SubArray is a view on
>: an Array with reference semantics. Further assume that Array and
>: SubArray are reference counted so that any shared memory is
>: automatically released when the last view on it is gone.

>It has occured to me that there is a continuing thread through all of
>the examples.  This one is much clearer.  What we have is a class plus
>a proxy class used for reference semantics.  The only standard example
>of a proxy class is vector<bool>/bvector (please no comments on which,
>that has been done and we all disagree) and its reference class.  Each
>of the examples in this thread used default constructed reference
>classes.  In bvector, there is no default constructor and the
>constructor which prohibits it is private and only available to
>bvector.  This problem can be solved by design.

>The second thing has been passing a reference class by reference to
>some function.  References to references are prohibited for the built
>in references; however, that is not true for proxy classes.  They are
>not real references and the safty built into the language for real
>references must be enforced by the programmer.

>I think this covers the common (mis)design in all examples.

>That still leaves open the copy constructor for a proxy class.  If it
>is private, proxies may not be passed to or returned by functions by
>value.  But by reference is wrong as above.  If it is not private, the
>user may copy construct a proxy class variable from the return of a
>member of the basic class using the proxy.  But reference variables
>are allowed in the language; so, I guess that I will need to accept
>proxy variables, just not default constructed ones.  The bvector
>copy-ctor is public.

Well, I suppose one could argue that the SubArray class acts more like
a built-in pointer class than a built-in reference...but I won't;
these are all valid points.

>And, of course, this has nothing to do with NRVO since I am convinced
>that it works on all reasonable classes.
>John

Yes, I am increasingly inclined to agree with you.

Though I still think that the example demonstrates the problem with
[class.copy] paragraph 15's flawed treatment of aliasing between
objects in the context of the NRVO,  I'd agree that to demonstrate it
takes examples that are so contrived and suffering from such other
design flaws, that it is probably of no practical significance in this
context.

I still think that a standard-specified optimisation should not change
programme semantics in this fashion, even for badly designed code, but
that is just my opinion.

(Now if [class.copy] paragraph 15 could just be limited to the
NRVO...)

Thanks for your comments,
Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: jpotter@falcon.lhup.edu (John E. Potter)
Date: 1997/05/06
Raw View
Brian Parker <bparker@gil.com.au> wrote:
: David Vandevoorde <daveed@vandevoorde.com> wrote:
: >Brian Parker wrote:

   [...]

: >>  I have seen discussions of this suggesting that the only problem is
: >> with side-effects in constructors and destructors and one shouldn't be
: >> relying upon these anyway. The change wrought by the NRV optimisation
: >> goes deeper than that, however- it effectively changes the semantics
: >> of a function from "return-by-value" to "return-by-reference".

: >That's not quite true: a value is still returned. The optimization
: >only affects when the allocation of the returned value occurs
: >and when/how it is constructed. You might write a copy-constructor
: >in such a way that the effects of the the function become
: >unexpected if the optimization is applied (or if it is not
: >applied, for that matter), but a value of the correct type
: >is returned in all cases.

: Yes, *a* value of the correct type is returned, just that it's the wrong
: value in the example I posted.
: Admittedly, I was using a little poetic licence when describing it as
: "return-by-reference"; my point is that the NRV optimisation changes an
: initialisation with a *copy* of a local variable to an initialisation with
: that exact object (or equivalently, with a bit-wise copy).

: >> The
: >> example code at the end of this post is a somewhat contrived but
: >> nevertheless reasonable example of a class that relies (quite rightly,
: >> I think) on return by value always returning a copy of a local
: >> variable (similar to call by value for function calls).

: >I disagree somewhat: if you do not explicitly specify a
: >copy-constructor, it should be the compiler's prerogative
: >to elide it. (I think it was Scott Meyers who pointed out
: >that ``NRV optimization'' is somewhat of a misnomer here:
: >the object being optimized away has no name).

I think you have the right person but the wrong statement.  Scott
noted that Stroustrup in D&E refers to it as RVO and only Lippman
refers to it as NRVO.  He went on to suggest that the g++ extension
might be called NRVO.  The documentation for USL cfront also refers
to it as RVO.  We do know what we are talking about though :-)

An example (something like ARM):
 C operator+ (C const& lhs, C const& rhs) {
  C temp(lhs);
  temp += rhs;
  return temp;
  }

This might be implemented as:
 void add (void* RawMemoryForC, C const& lhs, C const& rhs) {
  C temp(lhs);
  temp += rhs;
  new (RawMemoryForC) C(temp);
  }

And the RVO gives:
 void add (void* RawMemoryForC, C const& lhs, C const& rhs) {
  new (RawMemoryForC) C(lhs);
  *(C*)RawMemoryForC += rhs;
  }

The optimized away item was the named local variable temp.

Lippman had argued for writing the function as above while Meyers
recommended:

 C operator+ (C const& lhs, C const& rhs) {
  return C(lhs) += rhs;
  }

so that the optimized away item would not be a named local variable,
thus being more likely to be supported.  Following the Jul 96 meeting
he concluded that they were both correct.

: The fact remains that in the code I posted,

: File f2 = GetTwoFileHandles(f1, "test.txt");

: sets f2 to a completely different value than

: File f2;
: f2 = GetTwoFileHandles(f1, "test.txt");

: when the NRV optimisation is applied (assuming that an assignment operator
: equivalent to the copy constructor was defined- unfortunately I left that
: out of the posted example).
: If one is familiar with the mechanism of the NRV optimisation its effect can
: be predicted and worked around, but at the very least it can be
: counterintuitive. As I stated in my previous post, I would make the stronger
: assessment that it is simply wrong in some cases- I expect a value-returning
: function to always return a true copy of a returned local variable, not
: (effectively) a bit-wise copy.

 [ snip ]

: >I personally also prefer syntax for returned entities (a la GNU,
: >or a la Pascal). However, this optimization was blessed (if I
: >remember correctly) at the July 96 meeting, and at that time,
: >I think it was more judicious to establish existing practice,
: >than come up with yet another extension.

: I think the committee will need to revisit the issue anyway, as [class.copy]
: paragraph 15 will definitely need some changes IMHO. I would prefer to see
: that paragraph deleted and the NRV optimisation left outside the standard
: and require an explicit compiler switch to activate it where safe (which
: would be in most, but not all, codes); it seems to me that that gives the
: best of all worlds.

I must agree with another poster that it would be the worst of all worlds
in being unspecified.  As it is, it is a quality of implementation issue
and the marketplace will decide whether it is desirable.  Being allowed
by the standard, will force those wishing to write portable code, to
write in such a way as to work with or without it.  IMHO, a good idea.

FWIW:
g++ optimizes neither.
xlC does not optimize Stan's and screws up Scott's by casting away the
const on lhs, modifying and returning it.
USL cfront optimizes Stan's but refuses to compile Scott's, claiming that
there is no operator+= () const.  It is the only compiler
I have seen which has the RVO.  And it does have a documented switch
for enabling it.  If it writes the copy ctor, no RVO, if you write the
copy ctor, RVO.  Not what you want; however, it is a switch.

MO:  I think that most users would like to see as many temporaries as
possible removed, and the market will demand RVO.  I have seen several
classes lately which try to do different things depending upon which
constructor is used.  I think that Scott also said that the job of a
constructor is to construct an object.  I think that doing things like
copy on write should include another layer of abstraction and there
are good published ways to do it.  If the draft is approved as is, we
will all learn to design so that things will work whether copies are
made or not.  By C++ 2003, the issue will have been forgotten.

John
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Brian Parker <bparker@gil.com.au>
Date: 1997/05/06
Raw View
Paul DeRocco wrote:

(example snipped-see original post)

>I don't see this. GetTwoFileHandles conceptually deals with _three_ File
>objects, which are optimized down to two. There is the one that is
>passed by reference, and modified by the two assignments. There is the
>one that is created as a local variable. And there is one that is
>created as a copy that can be returned. It is the last two that are
>optimized into one object, with no effect on the one passed by
>reference. All the standard is saying is that it is legal for the
>compiler to detect that f will _always_ be returned as the value of the
>function, and instead construct it into a memory area supplied by the
>caller, instead of into its own stack frame. Whether it does this or
>not, I don't see that making the entirely separate File that was passed
>by reference point to stdout has any effect on the File that was
>returned.

Let me run through an example.
We want to ensure that all Files we get from GetTwoFileHandles() are
wrappers for *distinct* file handles so we can later redirect them without
unwanted interactions between file handles.
Without the NRV optimisation, the local variable "f" will be constructed
with some file handle, say 3. The passed-in reference parameter "file" which
is uninitialised just copies f's file handle as it knows f will be destroyed
on exit from the function. file now has a file handle of 3. The returned
File will use its copy constructor to duplicate a new file handle, say 4.
Therefore, on return from the function we have, as desired, two Files which
wrap two different file handles, 3 & 4.
If the NRV optimisation is enabled, however, f will have a file handle of 3
as before, and file will again have a file handle of 3, but this time the
returned File doesn't have its copy constructor called- it ends up with a
file handle of 3 and so after calling GetTwoFileHandles( ) we have two Files
wrapping the same file handle 3- not what we wanted.

This is a demonstration, in the context of the NRV optimisation, of a more
fundamental flaw with the optimisation suggested in section 12.8
[class.copy] paragraph 15. One *cannot* elide a copy of a variable that is
not used again unless it is also known that there is no aliasing or sharing
of resources between that object and any other object that may be used
again, where "resource" includes dynamically allocated memory, file handles,
graphics resources etc.

In that paragraph 15 as currently drafted does not make that restriction
then it is patently incorrect. But worse is the fact that, even if the draft
were ammended to make this clear, in general a compiler cannot detect all
forms of aliasing.

The example above introduces aliasing of a file handle between f and file in
a manner undetectable by a compiler. So even something as seemingly
innocuous as accessing an int member can invalidate the optimisation. And as
I discussed in the thread "Subtle bug in CD2 [class.copy]", if an object
uses dynamic memory, or has a non-trivial constructor etc. then the
optimisation is also unsafe. That litany just about rules out any use of the
NRV optimisation except for trivial cases that could be handled just as
easily by defining a computational constructor.

I am not saying that the NRV optimisation (or the more general optimisation
of paragraph 15) is not useful- just that it can be unsafe in the (uncommon)
examples given in which the aliasing could not be detected by the compiler.
For that reason, I don't think that it is appropriate as a draft-sanctioned
optimisation, but instead should be relegated to the ranks of unsafe
optimisations that require a (non-standard) explicit switch to enable-
especially when one considers that it introduces other sematic changes that
I have not mentioned viz. side-effects in constructors are elided.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: David Vandevoorde <daveed@vandevoorde.com>
Date: 1997/05/07
Raw View
John E. Potter wrote:
[...]
 > An example (something like ARM):
 >         C operator+ (C const& lhs, C const& rhs) {
 >                 C temp(lhs);
 >                 temp += rhs;
 >                 return temp;
 >                 }
 >
 > This might be implemented as:
 >         void add (void* RawMemoryForC, C const& lhs, C const& rhs) {
 >                 C temp(lhs);
 >                 temp += rhs;
 >                 new (RawMemoryForC) C(temp);
 >                 }
 >
 > And the RVO gives:
 >         void add (void* RawMemoryForC, C const& lhs, C const& rhs) {
 >                 new (RawMemoryForC) C(lhs);
 >                 *(C*)RawMemoryForC += rhs;
 >                 }

Well, except the compiler must still clean up
*(C*)RawMemoryForC if an exception is thrown prior to the
return-operation.

 Daveed
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Brian Parker <bparker@gil.com.au>
Date: 1997/05/07
Raw View
Oleg Zabluda wrote:

>Brian Parker <bparker@gil.com.au> wrote:
>: I would prefer to see
>: that paragraph deleted and the NRV optimisation left outside the standard
>: and require an explicit compiler switch to activate it where safe (which
>: would be in most, but not all, codes); it seems to me that that gives the
>: best of all worlds.

>I think it would be absolutely unacceptable. It's actualy the worst
>of all worlds. Both unpredictable performance and no standard way
>of determining determine where NRVO is prohibited.
>Oleg.

But worse would be if the NRV optimisation was allowed indiscriminantly by
the standard and there was no standard way to disable it if was giving
incorrect results. I would be tempted to agree with you if compilers could
be relied upon not to incorrectly apply the optimisation where there is
resource aliasing between the object which is having its copy elided and
some other object used later.

For example, I suspect that most compilers will not detect the aliasing of
file handles in the original example I posted. I would be interested if
someone could recompile that example on a compiler that supports the NRV
optimisation and post the results (it should be easily ported to use the
equivalent UNIX calls).

I still maintain that at the very least [class.copy] paragraph 15 needs to
make it clear that the optimisation can only be applied if there is no
semantic difference after applying it apart from possibly side-effects in
the elided copy-constructor and matching destructor (i.e. specify not just
that the object is not reused, but also that no part of the object or its
resources are reused)- as it stands it is incorrect.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: bparker@gil.com.au (Brian Parker)
Date: 1997/05/04
Raw View
As a related discussion to my previous post discussing what I perceive
to be flaws in [class.copy] paragraph 15 (see "Subtle bug in CD2
[class.copy]?"), I was wondering what peoples' views were on the named
return value (NRV) optimisation.

 I know that this has been discussed at length by the committee, but I
still find the idea of allowing "optimisations" that change the
semantics of a programme to be worrying precedent.

 I have seen discussions of this suggesting that the only problem is
with side-effects in constructors and destructors and one shouldn't be
relying upon these anyway. The change wrought by the NRV optimisation
goes deeper than that, however- it effectively changes the semantics
of a function from "return-by-value" to "return-by-reference". The
example code at the end of this post is a somewhat contrived but
nevertheless reasonable example of a class that relies (quite rightly,
I think) on return by value always returning a copy of a local
variable (similar to call by value for function calls). It is a
wrapper class for a raw file handle that duplicates the file handle on
copying so that streams can be later redirected without affecting
other File objects. When the NRV optimisation is enabled, however, the
return by value of function GetTwoFileHandles() is not performed and
hence the programme will exhibit quite different behaviour.

Allowing code to quietly change in this manner is a real gotcha. The
standard already allows a compiler to elide the copy if there is truly
no semantic difference, by the "as if" rule (though as discussed in my
previous post it would take an extremely smart compiler- bordering on
sentient- to know this), and there is already the work-around of
designing a special constructor for an object so a temporary can be
returned directly

If this optimisation is to be allowed, I think that it should either
be specified by some syntactic extension (ala G++) or only be allowed
as a non-standard extension, analogous to the "assume no aliasing"
optimisation switch of most compilers (on the PC at least), which the
programmer can choose to enable for particular functions where he or
she is certain no semantic changes will result (I think this is the
ideal solution- there really is no need IMHO for the standard to bend
over backwards to allow an optimisation- let the user decide whether
to enable it).

I think this really does have the potential to be the "no alias" of
the C++ standard; what do others think?

Example code follows-
------------------------------------------------------------------------------------------------------

// (Somewhat contrived) example of NRV's incorrect altering
// of semantics from
// return by value to return by reference in a manner undetectable by
// a compiler.
// Compiled under VC++ 5.0

#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>

// convenient wrapper class for a file handle
class File {
public:
 // create wrapper for file
 File(const char *sz) {
  file_handle = _open( "test.txt", _O_WRONLY | _O_CREAT, _S_IREAD |
_S_IWRITE );

  fOwner = true;
 }

 // default constructor
 File() {
  file_handle = 0;
  fOwner = false;
 }

 // copy-constructor
 // (make a duplicate copy of the underlying file handle so we can
later
 // manipulate the file handle independently)
 File(const File& f) {
  file_handle = _dup(f.file_handle);

  fOwner = true;
 }

 // get and assume ownership of file_handle (for calling legacy code)
 int GetFileHandle() {fOwner = false; return file_handle;}

 // close file if owned
 ~File() { if (fOwner) _close(file_handle);}

 // send any output using this File to std output
 void SetToStdOut() {_dup2( 1, file_handle );}

 // write  output to file
 void WriteSomething() {_write(file_handle, "test", 4);}
 void WriteSomethingElse() {_write(file_handle, "abcd", 4);}

 int file_handle;
 bool fOwner;
};

// Create two *independent* file handle wrappers for file_name
// Return one via the reference parameter "file" and just return the
other.
File GetTwoFileHandles(File& file, const char * file_name)
{
 File f(file_name); // create a temporary file wrapper

 file.file_handle = f.GetFileHandle();
 file.fOwner = true;

 // conveniently, assume ownership of f's file_handle.
 // This is safe because we are returning a separate copy by value.
 // (Note that a compiler can't know that a file handle is being
 // shared here- it looks just like an int being returned by value).

 return f; // return a copy of f
}


int main()
{
 File f1;

 File f2 = GetTwoFileHandles(f1, "test.txt");

 // we now have two separate handles on file "test" so
 // we can use them independently

 f1.WriteSomething();
 f2.WriteSomething();

 f1.SetToStdOut(); // send all output from this stream f1 to std
    // output
    // but leave f2 connected to the file

 f1.WriteSomethingElse();
 f2.WriteSomethingElse();

 // But if the NRV optimisation is applied then the behaviour is
silently
 // changed to f1 and f2 being wrappers for the *same* file handle and
the previous
 // call will change the output of *both* file handles to std output.
 // The function GetTwoFileHandles( ) has effectively been changed by
the NRV "optimisation"
 // from return by value to return by reference.

 return 0;
}

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Brian Parker <bparker@gil.com.au>
Date: 1997/05/05
Raw View
David Vandevoorde <daveed@vandevoorde.com> wrote:
>Brian Parker wrote:
>[...]
>>  I have seen discussions of this suggesting that the only problem is
>> with side-effects in constructors and destructors and one shouldn't be
>> relying upon these anyway. The change wrought by the NRV optimisation
>> goes deeper than that, however- it effectively changes the semantics
>> of a function from "return-by-value" to "return-by-reference".
>
>That's not quite true: a value is still returned. The optimization
>only affects when the allocation of the returned value occurs
>and when/how it is constructed. You might write a copy-constructor
>in such a way that the effects of the the function become
>unexpected if the optimization is applied (or if it is not
>applied, for that matter), but a value of the correct type
>is returned in all cases.

Yes, *a* value of the correct type is returned, just that it's the wrong
value in the example I posted.
Admittedly, I was using a little poetic licence when describing it as
"return-by-reference"; my point is that the NRV optimisation changes an
initialisation with a *copy* of a local variable to an initialisation with
that exact object (or equivalently, with a bit-wise copy).

>
>> The
>> example code at the end of this post is a somewhat contrived but
>> nevertheless reasonable example of a class that relies (quite rightly,
>> I think) on return by value always returning a copy of a local
>> variable (similar to call by value for function calls).
>
>I disagree somewhat: if you do not explicitly specify a
>copy-constructor, it should be the compiler's prerogative
>to elide it. (I think it was Scott Meyers who pointed out
>that ``NRV optimization'' is somewhat of a misnomer here:
>the object being optimized away has no name).
>

The fact remains that in the code I posted,

File f2 = GetTwoFileHandles(f1, "test.txt");

sets f2 to a completely different value than

File f2;
f2 = GetTwoFileHandles(f1, "test.txt");

when the NRV optimisation is applied (assuming that an assignment operator
equivalent to the copy constructor was defined- unfortunately I left that
out of the posted example).
If one is familiar with the mechanism of the NRV optimisation its effect can
be predicted and worked around, but at the very least it can be
counterintuitive. As I stated in my previous post, I would make the stronger
assessment that it is simply wrong in some cases- I expect a value-returning
function to always return a true copy of a returned local variable, not
(effectively) a bit-wise copy.

>If you really want a copy-constructor to be called, use:
> X result(x);
> return result;
>instead of:
> return x;
>
>[...]

Actually, the way sec 12.8 [class.copy] paragraph 15 is written, I think a
compiler would be allowed to elide the copy into result and just return x
directly so this work-around wouldn't work (but that is more a bug with that
paragraph than with the NRV optimisation itself).

...

>I personally also prefer syntax for returned entities (a la GNU,
>or a la Pascal). However, this optimization was blessed (if I
>remember correctly) at the July 96 meeting, and at that time,
>I think it was more judicious to establish existing practice,
>than come up with yet another extension.
>
> Daveed

I think the committee will need to revisit the issue anyway, as [class.copy]
paragraph 15 will definitely need some changes IMHO. I would prefer to see
that paragraph deleted and the NRV optimisation left outside the standard
and require an explicit compiler switch to activate it where safe (which
would be in most, but not all, codes); it seems to me that that gives the
best of all worlds.

,Brian Parker (bparker@gil.com.au)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: "Paul D. DeRocco" <strip_these_words_pderocco@ix.netcom.com>
Date: 1997/05/05
Raw View
Brian Parker wrote:

[long snip]

> // Create two *independent* file handle wrappers for file_name
> // Return one via the reference parameter "file" and just return the
> other.
> File GetTwoFileHandles(File& file, const char * file_name)
> {
>         File f(file_name);      // create a temporary file wrapper
>
>         file.file_handle = f.GetFileHandle();
>         file.fOwner = true;
>
>         // conveniently, assume ownership of f's file_handle.
>         // This is safe because we are returning a separate copy by value.
>         // (Note that a compiler can't know that a file handle is being
>         // shared here- it looks just like an int being returned by value).
>
>         return f;       // return a copy of f
> }
>
> int main()
> {
>         File f1;
>
>         File f2 = GetTwoFileHandles(f1, "test.txt");
>
>         // we now have two separate handles on file "test" so
>         // we can use them independently
>
>         f1.WriteSomething();
>         f2.WriteSomething();
>
>         f1.SetToStdOut();       // send all output from this stream f1 to std
>                                 // output
>                                 // but leave f2 connected to the file
>
>         f1.WriteSomethingElse();
>         f2.WriteSomethingElse();
>
>         // But if the NRV optimisation is applied then the behaviour is
> silently
>         // changed to f1 and f2 being wrappers for the *same* file handle and
> the previous
>         // call will change the output of *both* file handles to std output.
>         // The function GetTwoFileHandles( ) has effectively been changed by
> the NRV "optimisation"
>         // from return by value to return by reference.
>
>         return 0;
> }

I don't see this. GetTwoFileHandles conceptually deals with _three_ File
objects, which are optimized down to two. There is the one that is
passed by reference, and modified by the two assignments. There is the
one that is created as a local variable. And there is one that is
created as a copy that can be returned. It is the last two that are
optimized into one object, with no effect on the one passed by
reference. All the standard is saying is that it is legal for the
compiler to detect that f will _always_ be returned as the value of the
function, and instead construct it into a memory area supplied by the
caller, instead of into its own stack frame. Whether it does this or
not, I don't see that making the entirely separate File that was passed
by reference point to stdout has any effect on the File that was
returned.

--

Ciao,
Paul

(Please send e-mail to mailto:pderocco@ix.netcom.com instead of the
return address, which has been altered to foil junk mail senders.)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1997/05/06
Raw View

Brian Parker <bparker@gil.com.au> wrote:
: I would prefer to see
: that paragraph deleted and the NRV optimisation left outside the standard
: and require an explicit compiler switch to activate it where safe (which
: would be in most, but not all, codes); it seems to me that that gives the
: best of all worlds.

I think it would be absolutely unacceptable. It's actualy the worst
of all worlds. Both unpredictable performance and no standard way
of determining determine where NRVO is prohibited.

Oleg.
--
Life is a sexually transmitted, 100% lethal disease.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]