const C operator+(const C& c1, const C& c2)
{
C c(c1);
c+= c2;
return c;
}
Or maybe (in Meyers' style)
const C operator+(const C& c1, const C& c2)
{
return C(c1) += c2;
}
But what about this:
const C operator+(C c1, const C& c2)
{
return c1+= c2;
}
I think it is simpler and it demands less operations
than the other methods. But I don't seem to find this
style anywhere. Maybe there is a problem with inlining
it or optimizing it. What about making a template out
of it? Any idea?
Michel Michaud mic...@removethis.mail2.cstjean.qc.ca
http://www3.sympatico.ca/michel.michaud
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
Michel Michaud wrote in message
<1OrL1.4153$lR.15...@news21.bellglobal.com>...
>But what about this:
>
>const C operator+(C c1, const C& c2)
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
One BIG problem with this style - operator += will CHANGE
object c1. A user who does a = b + c; will NOT be expecting
this expression to change the value of b, but your
definition of opererator+ will do that.
This is why you don't see this version of operator+.
-Chris
-----== Posted via Deja News, The Leader in Internet Discussion ==-----
http://www.dejanews.com/rg_mkgrp.xp Create Your Own Free Member Forum
It works even if it is, it works in every case any other would
work... Try again :)
>Michel Michaud wrote in message
><1OrL1.4153$lR.15...@news21.bellglobal.com>...
>
>>But what about this:
>>
>>const C operator+(C c1, const C& c2)
>
Michel Michaud mic...@removethis.mail2.cstjean.qc.ca
http://www3.sympatico.ca/michel.michaud
What has the const qualification got to do with pass by value? (it has
something in the case of return by value, but that is not the issue
here)
>
>Michel Michaud wrote in message
><1OrL1.4153$lR.15...@news21.bellglobal.com>...
>
>>But what about this:
>>
>>const C operator+(C c1, const C& c2)
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
Did you read his code? Actually the problem is the cost of a pass by
value. But I think if you do a full analysis you will discover that to
be an illusion (you have to copy internally other wise)
>
>This is why you don't see this version of operator+.
Francis Glassborow Chair of Association of C & C++ Users
If it's a good thing to pass c2 by "const&", then why not c1?
If it's a good thing to pass c1 by value, then why not c2?
Anyway, returning const C is entirely meaningless; just returning
"C" has identical semantics, but with less clutter.
--
Nathan Myers
n...@nospam.cantrip.org http://www.cantrip.org/
I believe that this will work in every place that the "classic"
implementation of operator+ works, but your version exposes the
implementation of operator+ to the clients of C through its interface.
If you had some super nifty neat fast way to reimplement operator+() on C
objects that didn't require creation of a copy of c1, you'd either have to
change the interface to accept a const C & and force all clients to recompile
or live with the unnecessary and wasteful copy construction of c1.
-----== Posted via Deja News, The Leader in Internet Discussion ==-----
http://www.dejanews.com/rg_mkgrp.xp Create Your Own Free Member Forum
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
>> But what about this:
>>
>> const C operator+(C c1, const C& c2)
>> {
>> return c1+= c2;
>> }
>>
>> I think it is simpler and it demands less operations
>> than the other methods. But I don't seem to find this
>> style anywhere. Maybe there is a problem with inlining
>> it or optimizing it. What about making a template out
>> of it? Any idea?
(snip)
>One BIG problem with this style - operator += will CHANGE
>object c1. A user who does a = b + c; will NOT be expecting
>this expression to change the value of b, but your
>definition of opererator+ will do that.
>
>This is why you don't see this version of operator+.
I believe that you are correct if the operator is declared thusly:
const C operator+( C& c1, const C& c2 );
but in this example the first argument (c1) is a pass-by-value
argument, in which case if the user does a = b + c will not see the
value of b change, if I understand this correctly. Isn't that right?
------
Remove "nospam" to respond via email.
Matt
matt...@nospam.us.ibm.com
------
- These views are my own, not necessarily those of my company.
>> const C operator+(C c1, const C& c2)
>One BIG problem with this style - operator += will CHANGE
>object c1. A user who does a = b + c; will NOT be expecting
>this expression to change the value of b, but your
>definition of opererator+ will do that.
No, in "a=b+c", a copy of 'b' is made using C::C(const C&) and passed
to the function operator+ as the variable 'c1'. It doesn't matter
whether 'b' is const or not. It will not be changed!
--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------
> > const C operator+(C c1, const C& c2)
> > {
> > return c1+= c2;
> > }
> >
> One BIG problem with this style - operator += will CHANGE
> object c1.
It will not. Object c1 is passed by value.
By the whay, I don't understand why return value is of type
"const C" and not simply C.
This is why this form of operators is never used for large objects.
For smaller objects passing by value is not an issue, but there may
be other reasons (efficiency, code elegance, etc.).
I cannot see an example when the code above is better than
C operator+ (C const& c1, C const& c2)
{
C temp(c1);
temp += c2;
return temp;
};
Best regards.
Ivan Krivyakov, software engineer
But I believe that any other version will be less efficient. Take the
classic one : pass by ref. then copy to a new object, to me that's one
more operation then calling the copy constructor to pass a copy of the
value. But I am not so sure that it will be better, nor worse. Every
book I read (you've seen more) don't even mention it. Could it be that
Stroustrup, Lippman, Meyers did not think about it?
It clearly is a simpler solution if not a better one, so there must
be something "bad" with it (style maybe? ... can of worms).
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
>> const C operator+(C c1, const C& c2)
>> {
>> return c1+= c2;
>> }
>>
>One BIG problem with this style - operator += will CHANGE
>object c1. A user who does a = b + c; will NOT be expecting
>this expression to change the value of b, but your
>definition of opererator+ will do that.
>
>This is why you don't see this version of operator+.
PLEASE be serious and don't waste your time answering without
thinking a bit more about it. You read (C& c1,...) but
there is no & in my code. I know, it happens :)
> Michel Michaud<mic...@removethis.mail2.cstjean.qc.ca> wrote:
>> John Isaacks :
>>> Michel Michaud<mic...@removethis.mail2.cstjean.qc.ca> wrote:
>>>> But what about this:
>>>> const C operator+(C c1, const C& c2)
>>> It works as long as c1 is not const in the calling program
>> It works even if it is, it works in every case any other would
>> work... Try again :)
> If it's a good thing to pass c2 by "const&", then why not c1?
> If it's a good thing to pass c1 by value, then why not c2?
Because you *must* copy the first argument, to run operator+= on a
copy of it, but you don't have to copy c2. That's the whole point.
> Anyway, returning const C is entirely meaningless; just returning
> "C" has identical semantics, but with less clutter.
This was discussed in this newsgroup already (or was it comp.std.c++?)
--
Alexandre Oliva
mailto:ol...@dcc.unicamp.br mailto:aol...@acm.org
http://www.dcc.unicamp.br/~oliva
Universidade Estadual de Campinas, SP, Brasil
>If it's a good thing to pass c2 by "const&", then why not c1?
>If it's a good thing to pass c1 by value, then why not c2?
The whole point of operator+(value,const_reference) is just to ease
the notation inside the body of operator+. It doesn't make things
any faster.
If we wanted to implement a+b as "copy(b)+=a", which is non-standard
but won't have practical consequences in many situations, we should
use operator+(const_reference,value).
If we might use either method or some other method, then we should
just use operator+(const_reference,const_reference) and risk making
the body of operator+ a little more ugly (by the explicit copy ctor
call, which, IMHO, is not that ugly anyway).
But the best method is to have operator constructors.
--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> Anyway, returning const C is entirely meaningless; just returning
> "C" has identical semantics, but with less clutter.
Well do you want a + b = c to be allowed ?
It's a question of taste, but it makes a semanticall difference.
--
Valentin Bonnard mailto:bonn...@pratique.fr
info about C++/a propos du C++: http://pages.pratique.fr/~bonnardv/
Oddly, no. const qualification of your return by value is actually
essential for some idioms to work. Several years ago C++ made a change
that allows you to 'dereference a const return value' To see why this
might matter consider:
MyType a, b, c, ans;
// code that initialises a, and b
ans = a+b+c;
this, for user defined types if operator+ is member function:
ans.operator=(a.operator+(b).operator+(c));
and that only works if you can call a member function on the return
value. Even if you return by value you can do so if the value is const
qualified. I hope they never changed that further because calling non-
const member functions on temporaries would seem dangerous to me.
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Nathan Myers a écrit dans le message
<6toj2i$ar1$1...@shell7.ba.best.com>...
>Michel Michaud<mic...@removethis.mail2.cstjean.qc.ca> wrote:
>>John Isaacks :
>>>Michel Michaud<mic...@removethis.mail2.cstjean.qc.ca> wrote:
>>>>But what about this:
>>>>
>>>>const C operator+(C c1, const C& c2)
>>
>>>It works as long as c1 is not const in the calling program
>>
>>It works even if it is, it works in every case any other would
>>work... Try again :)
>
>If it's a good thing to pass c2 by "const&", then why not c1?
>If it's a good thing to pass c1 by value, then why not c2?
Well well well, now I know why this style is not used ! Nobody
seems to understand it!
Let me explain: c2 is passed by ref because I don't need a copy.
c1 is pass by value because I need an object to modify and return.
Either I do it like this, or I make a local object in the function.
There is no way out of it (see Meyers - you must have read it!). So
my question is why choose the classic style over this one? There
must be a reason, I surely can't be right over Stroustrup, Meyers...
>Anyway, returning const C is entirely meaningless; just returning
>"C" has identical semantics, but with less clutter.
Again see Meyers EC++ 2ed p.92.
>--
>Nathan Myers
I was happy someone like you had answered me. No luck yet (you'll
surely reply, thank you for it, sincerely).
My understanding was that returning a const value prevents you from
calling
non-const functions on the unnamed return variable. If you returned a
const
C, the following code is a compiler error: C myc1, myc2; (myc1 +
myc2).non_const_C_member_fun();
Much in the same way this code is a compiler error:
int myint1, myint2;
++(myint1 + myint2);
-----== Posted via Deja News, The Leader in Internet Discussion ==-----
http://www.dejanews.com/rg_mkgrp.xp Create Your Own Free Member Forum
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
This is only an issue if you want to be able to take a previously
compiled client module that uses the above operator, and link it with a
new version of the library that contains type C's implementation. In
general, though, when people get a new version of a library, they
recompile their code anyway. (Unless of course it's a DLL.)
Actually, I'd expect the above code to be declared inline in a header,
since it's whole point is to define one function as a minor variant on
another possibly complicated non-inline function. If this were the case,
then the client code would have to be recompiled anyway, in order to
take advantage of any niftier new implementation of operator+.
--
Ciao,
Paul
I don't think there will be any difference (neither does my compilers)
between:
struct T {
T(int=0);
T(const T&);
const T& operator+=(const T&) {...;return *this;}
};
const T operator+(T l,const T&r) {return l+=r;}
and
const T operator+(const T&l,const T&r) {return T(l)+=r;}
or even
const T operator+(const T&l,const T&r) {
T t(l);
t+=r;
return t;
};
_IF_ you call it as
T t1,t2;
(t1+t2);
All of them call the copy-constructor (either explictly or as part of
call by value, then operator += and finally a copy-constructor to
return the value (the last copy-constructor might be avoided).
However, you're right for the case:
T t;
(1+t);
Here your idea avoids first calling T(1) and then copy-constructing
this value.
I do, however, agree with another poster that this unncessarily
exposes users of operator+ to its implementation.
--
// Home page http://www.dna.lth.se/home/Hans_Olsson/
// Email To..Hans.Olsson@dna.lth.se [Please no junk e-mail]
Hmm? I disagree.
struct C {
void hello() { cout << "C::hello\n"; }
void hello() const { cout << "const C::hello\n"; }
};
int main()
{
C a, b;
(a + b).hello(); // had better print "const C::hello"
}
Now, as to why you would want to force temporaries of C to be const,
I'm not sure. Simply creating some classes (e.g., network socket
objects) causes side effects; on these classes, you shouldn't be
returning a temporary at all.
You mean because they will guess what I do? I could just be stupid!
Anyway why would it matter? (Maybe I misinterpret what you're saying)
>If you had some super nifty neat fast way to reimplement operator+() on C
>objects that didn't require creation of a copy of c1, you'd either have to
>change the interface to accept a const C & and force all clients to
recompile
>or live with the unnecessary and wasteful copy construction of c1.
Of course, but this is clearly not possible for all but trivial classes
(or + operation). So it is safe to assume you need at least one call to
the constructor and that it's not such a bad place to put it.
BTW your reply and objections seems to be the most "serious" until now.
Congratulations. As I said, the reason to use the standard way is that
this way fools too many people... :)
Pass by value implies calling the constructor to create a new
object. But here I am only changing the place where it will
be done. No penalty. As for code elegance...
>I cannot see an example when the code above is better than
>
>C operator+ (C const& c1, C const& c2)
>{
> C temp(c1);
> temp += c2;
> return temp;
>};
The opposite may also be true (no example where this code is better).
So main reason to prefer one or the other? That's what I'm looking for.
There must be good reasons...
The original example exploited the fact that c1 was passed by value as a
way of forcing a copy, so that c1 could then be modified with +=. Looks
like a reasonable idea to me.
--
Ciao,
Paul
Just the two extra lines of code, plus the need to name the temporary.
The compiled code may wind up being the same, but the point here is to
make the source code simpler.
This is the style I prefer.
>I think it is simpler and it demands less operations
>than the other methods.
In most cases it will require the same number of operations as the other
methods. In some cases it can prevent the copy constructor from being
called at all where the other forms would have added a copy constructor.
At
least this was the case in CD2. I'm less sure about the rule in the
final
standard.
If the body of operator+ is not inline it is pretty clear that the other
forms must invoke a copy constructor. However CD2 said that a
call-by-value
actual argument did not have to be copied if the compiler could tell
that
the original value would no longer be used.
I believe that in the final standard they got rid of some instances
where
copy constructors could be skipped, but I don't know if this was one of
them.
> In article <6toj2i$ar1$1...@shell7.ba.best.com>,
> n...@nospam.cantrip.org (Nathan Myers) wrote:
> [snip]
> > Anyway, returning const C is entirely meaningless; just returning
> > "C" has identical semantics, but with less clutter.
>
> My understanding was that returning a const value prevents you from
> calling
> non-const functions on the unnamed return variable. If you returned a
> const
> C, the following code is a compiler error: C myc1, myc2; (myc1 +
> myc2).non_const_C_member_fun();
>
> Much in the same way this code is a compiler error:
> int myint1, myint2;
> ++(myint1 + myint2);
Perhaps this is a better demo of your point:
#include <iostream>
#include <string>
int main()
{
std::string s1("Hi");
std::string s2("There");
std::string s3 = (s1 + s2).insert(2, 1, ' ');
std::cout << s3;
> In article <1OrL1.4153$lR.15...@news21.bellglobal.com>,
> "Michel Michaud" <mic...@removethis.mail2.cstjean.qc.ca> wrote:
>> const C operator+(C c1, const C& c2)
> your version exposes the implementation of operator+ to the clients
> of C through its interface.
I don't think this is a problem; operator+ with two arguments is never
part of the interface of a class, since it cannot be a member
function. Therefore, there's no risk that modifying its interface
will require modification of overriders, since non-member functions
cannot be overridden in subclasses.
--
Alexandre Oliva
mailto:ol...@dcc.unicamp.br mailto:aol...@acm.org
http://www.dcc.unicamp.br/~oliva
Universidade Estadual de Campinas, SP, Brasil
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
You have to make a copy of one. By declaring it by value, you let
the user make the copy. You may even avoid the copy; if the argument
is a temporary, the compiler will construct it immediately into the
parameter.
Which one you make pass by value, of course, doesn't matter for addition.
It does, however, for subtraction.
> Anyway, returning const C is entirely meaningless; just returning
> "C" has identical semantics, but with less clutter.
Not true if you call a member function on the return value. With a
const return value, an expression like (a + b) += c is illegal; with
a non-const, it isn't (supposing the classical definition of op+= as
a non-const member function).
Just another way of making user defined operators more like their
built-in counterparts, I guess.
--
James Kanze +33 (0)1 39 23 84 71 mailto: ka...@gabi-soft.fr
+49 (0)69 66 45 33 10 mailto: jka...@otelo.ibmmail.com
GABI Software, 22 rue Jacques-Lemercier, 78000 Versailles, France
Conseils en informatique orientée objet --
-- Beratung in objektorientierter Datenverarbeitung
-----== Posted via Deja News, The Leader in Internet Discussion ==-----
http://www.dejanews.com/rg_mkgrp.xp Create Your Own Free Member Forum
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
To illustrate what I mean better, let's take the example of "classic"
operator+(). In this example, assume no inlining of functions:
1) const C operator+(const C &, const C &) is the "interface" of
operator+() on C objects. All users of operator+() on C objects
care deeply about this. If anything with the signature of this
interface changes, all users of operator+() on C objects will be
forced to recompile, not just relink. If there are many users
of operator+() on C objects, this recompilation time could be
significant.
2)
{
return C(c1)+=c2;
}
is the "implementation" of operator+() on C objects. Assuming
this is not an inline function, this could change without the
users being forced to recompile. Only a relink would be necessary.
The responsibility of creating a copy of c1 and applying member
function operator+=() to that copy is wholly on the implementation.
In your example, the creation of the copy of c1 is done in the
interface, not in the implementation. In other words, the creation
of the copy of c1 is done at the _users_ side, not in the implementation.
This exposes half of your implementation to the user through your
interface. If you wanted to change that half of your implementation,
you'd either have to change your interface or suffer through a bad
interface (extra copy construction), which was my original point.
I'll admit that it would take a contrived example to illustrate.
In most cases, you'd want operator+() to be an inline function
anyway, which would bind all users to your interface and implementation.
I think that your implementation of operator+() in terms of
operator+=() is interesting, but I'll stick with using "classic"
operator+().
----------
Kevin Kostrzewa
kkost...@csisolutions.com
> There
> must be a reason, I surely can't be right over Stroustrup, Meyers...
It's not a question of being right over anyone; as far as I know, no
one has condemned it. The just didn't think of it. (I'll admit it
seemed strange to me, until I thought about it.)
On the other hand, you are right to ask about it. Traditionally, one
only publishes the ideas that work. So the question is: do they not
mention it because they didn't think of it, or because they found that
it didn't work?
--
James Kanze +33 (0)1 39 23 84 71 mailto: ka...@gabi-soft.fr
+49 (0)69 66 45 33 10 mailto: jka...@otelo.ibmmail.com
GABI Software, 22 rue Jacques-Lemercier, 78000 Versailles, France
Conseils en informatique orientée objet --
-- Beratung in objektorientierter Datenverarbeitung
-----== Posted via Deja News, The Leader in Internet Discussion ==-----
{Francis is right; see also GotW #27 at www.cntc.com/resources. -hps}
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Right.
>c1 is pass by value because I need an object to modify and return.
>Either I do it like this, or I make a local object in the function.
>There is no way out of it (see Meyers - you must have read it!).
OK so far.
>So my question is why choose the classic style over this one? There
>must be a reason, I surely can't be right over Stroustrup, Meyers...
There are two alternatives:
const C operator+(const C& c1, const C& c2)
{ return C(c1) += c2; }
const C operator+(C c1, const C& c2)
{ return c1 += c2; }
Each requires a call to C::C(const C&) to copy c1. Each requires a
call to operator+=. Each requires another call to C::C(const C&) to
copy out the result, which _may_ be optimized away. If you compiled
these two functions you _might_ find exactly identical assembly code.
An optimizer might not assume, though, that it can share storage for
c1 and the result, so the latter form might actually generate the second
copy, and thus poorer code.
The "classic" style declares the arguments symmetrically, which
communicates to users more accurately the function's intent.
Furthermore, optimizers recognize it.
>>Anyway, returning const C is entirely meaningless; just returning
>>"C" has identical semantics, but with less clutter.
>Again see Meyers EC++ 2ed p.92.
OK. For me, applying non-const operations to the result of operator+
seems fine, but some reasonable people disagree.
--
Nathan Myers
n...@nospam.cantrip.org http://www.cantrip.org/
>> In article <6tm7pa$cpq$1...@nnrp1.dejanews.com>, tav...@connix.com writes
>>> One BIG problem with this style - operator += will CHANGE
>>> object c1. A user who does a = b + c; will NOT be expecting
>>> this expression to change the value of b, but your
>>> definition of opererator+ will do that.
>>
>> Did you read his code? Actually the problem is the cost of a pass by
>> value. But I think if you do a full analysis you will discover that to
>> be an illusion (you have to copy internally other wise)
>>>
Michel> Thank you very much for reading my code. Two bad answers in a row
Michel> (before
Michel> your correct analysis), maybe that's the reason for not using it!
Michel> But I believe that any other version will be less efficient. Take the
Michel> classic one : pass by ref. then copy to a new object, to me that's one
Michel> more operation then calling the copy constructor to pass a copy of the
Michel> value. But I am not so sure that it will be better, nor worse. Every
Michel> book I read (you've seen more) don't even mention it. Could it be that
Michel> Stroustrup, Lippman, Meyers did not think about it?
Actually you do get a detailled discussion on constructor semantics in
"Inside The C++ Object Model"
Stan Lippman, Addison Wesley
He made an extensive discussion about some common optimizations. It is
likely that
T operator+ (const T& a, const T& b) { return T(a) += b; }
gets easily optimized (at least, it is already present in EGCS and I
do make extensive use of it).
-- Gabriel
>> This is why this form of operators is never used for large objects.
>> For smaller objects passing by value is not an issue, but there may
>> be other reasons (efficiency, code elegance, etc.).
Michel> Pass by value implies calling the constructor to create a new
Michel> object. But here I am only changing the place where it will
Michel> be done. No penalty. As for code elegance...
I'm afraid I don't get your point.
>> I cannot see an example when the code above is better than
>>
>> C operator+ (C const& c1, C const& c2)
>> {
>> C temp(c1);
>> temp += c2;
>> return temp;
>> };
Michel> The opposite may also be true (no example where this code is better).
Michel> So main reason to prefer one or the other? That's what I'm looking for.
Michel> There must be good reasons...
The code shown above requires the so called "Named Return Value
Optimization" to be properly optimized. It's beleived to be widely
implemented but I'm unable to find it in EGCS.
>But I believe that any other version will be less efficient. Take the
>classic one : pass by ref. then copy to a new object, to me that's one
>more operation then calling the copy constructor to pass a copy of the
>value. But I am not so sure that it will be better, nor worse. Every
>book I read (you've seen more) don't even mention it. Could it be that
>Stroustrup, Lippman, Meyers did not think about it?
>It clearly is a simpler solution if not a better one, so there must
>be something "bad" with it (style maybe? ... can of worms).
The question, I believe, was choosing between
C operator+(const C& c1, const C& c2) // traditional
{
C temp(c1);
temp += c2;
return temp;
}
C operator+(C c1, const C& c2) // alternative
{
c1 += c2;
return c1;
}
There was some side discussion about whether the return type
is or should be const, but let's ignore that.
No matter what, you need to create a fresh object to return
by value. The traditional example creates it inside the function,
initializing it with a reference to the first parameter.
The alternative example has the calling function create a temp
as a value parameter, which is then used as the temp for returning.
One or the other might be more efficient depending on the
exact calling conventions and the complexity of type C.
Any differences are likely to be small, however. On some
systems the generated code may be identical for both.
The traditional example is symmetrical in its parameters, but
the alternative is not. That alone causes readers of your code to
wonder why. In fact, several people who responded to the question
mis-read the alternative version. If they were maintenance
programmers, they would have wasted time and effort "fixing"
your code. Bear in mind that the "code reader" or "maintenance
programmer" is likely to be you after you have forgotten
about the trick you were using.
Clarity and simplicity should be preferred unless there is a
specific reason to use trickier code. I don't see how an unsymmetric
interface to a symmetric operation can be considered clearer or
simpler. The traditional example is simple and clearly correct.
--
Steve Clamage, stephen...@sun.com
That would be mostly true in the case of passing a "const C" parameter,
but a "const C" return value is different. Whether or not one should be
able to modify a returned temporary may be an arguable stylistic choice,
but it is a meaningful choice. From the CNTC coding standards:
- operators (member and free):
[...]
- arithmetic and assignment:
- always write +, -, etc. to return a const temporary
Our standards distinguish between "always" and "prefer." This one is
currently an "always," because we have decided to deplore the practice
of modifying temporaries (e.g., code like "(a+b)=c").
Note, however, the following related gotcha with const and return values
(mentioned in a later section in our standards):
- when you implement assignment, always implement the complete
protocol:
- declare copy assignment as "T& T::operator=(const T&)"
- never return const T&; although this would be nice since
it prevents usage like "(a=b)=c", it would mean you
couldn't portably put T objects into standard library
containers, since these require that assignment returns
a plain T& (Cline95: 212; Murray93: 32-33)
[...]
So in this case there's a subtle reason not to tack "const" onto the
return type.
Herb
---
Herb Sutter (mailto:he...@cntc.com)
Current Network Technologies Corp 2695 North Sheridan Way, Suite 150
www.cntc.com www.peerdirect.com Mississauga Ontario Canada L5K 2N6
There is one case where the call by value is better: if the caller is passing
a temporary. For example, the expression a+b+c, which is
operator+( operator+( a , b ) , c )
When passing a reference, the compiler must build the return value into
a temporary, and pass the reference to that. This temporary will then
be copied. With pass by value, the compiler will probably construct the
return value of the first call directly into argument, thus saving a copy.
--
James Kanze +33 (0)1 39 23 84 71 mailto: ka...@gabi-soft.fr
+49 (0)69 66 45 33 10 mailto: jka...@otelo.ibmmail.com
GABI Software, 22 rue Jacques-Lemercier, 78000 Versailles, France
Conseils en informatique orientée objet --
-- Beratung in objektorientierter Datenverarbeitung
-----== Posted via Deja News, The Leader in Internet Discussion ==-----
http://www.dejanews.com/rg_mkgrp.xp Create Your Own Free Member Forum
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> In article <6toj2i$ar1$1...@shell7.ba.best.com>,
> n...@nospam.cantrip.org (Nathan Myers) wrote:
> > Michel Michaud<mic...@removethis.mail2.cstjean.qc.ca> wrote:
> > >John Isaacks :
> > >>Michel Michaud<mic...@removethis.mail2.cstjean.qc.ca> wrote:
> > >>>But what about this:
> > >>>
> > >>>const C operator+(C c1, const C& c2)
> > >
> > >>It works as long as c1 is not const in the calling program
> > >
> > >It works even if it is, it works in every case any other would
> > >work... Try again :)
> >
> > If it's a good thing to pass c2 by "const&", then why not c1?
> > If it's a good thing to pass c1 by value, then why not c2?
>
> You have to make a copy of one. By declaring it by value, you let
> the user make the copy. You may even avoid the copy; if the argument
> is a temporary, the compiler will construct it immediately into the
> parameter.
>
> Which one you make pass by value, of course, doesn't matter for addition.
But it does. If you declare operator+ as asymmetric as the above,
you'll find that its behaviour with regard to type conversions is no
longer commutative.
Personally, I'd find that more annoying than the outsmarting of the
optimizer would be worth in most cases.
--
David Kastrup Phone: +49-234-700-5570
Email: d...@neuroinformatik.ruhr-uni-bochum.de Fax: +49-234-709-4209
Institut für Neuroinformatik, Universitätsstr. 150, 44780 Bochum, Germany
> Thank you very much for reading my code. Two bad answers in a row
> (before
> your correct analysis), maybe that's the reason for not using it!
Perhaps. Or perhaps people first write the declaration, use const
reference, then implement it, and don't bother changing the
declaration in the header file (so they stick with const reference
arguments).
> But I believe that any other version will be less efficient. Take the
> classic one : pass by ref. then copy to a new object, to me that's one
> more operation then calling the copy constructor to pass a copy of the
> value. But I am not so sure that it will be better, nor worse. Every
> book I read (you've seen more) don't even mention it. Could it be that
> Stroustrup, Lippman, Meyers did not think about it?
>
> It clearly is a simpler solution if not a better one, so there must
> be something "bad" with it (style maybe? ... can of worms).
Well, there is reason to use your solution: efficiency.
y = B() + x;
If op+ takes a const B& and isn't inlined, the compiler
won't see anything and will make a copy of the B() rvalue !
That's of course silly.
However, if op+ takes a B, even if the operator ins't
inlined, the B() can be constructed in place (by merging
B() and the function argument), saving a copy ctor call.
--
Valentin Bonnard mailto:bonn...@pratique.fr
info about C++/a propos du C++: http://pages.pratique.fr/~bonnardv/
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
<6trjms$evv$1...@nnrp1.dejanews.com>...
>In your example, the creation of the copy of c1 is done in the
>interface, not in the implementation. In other words, the creation
>of the copy of c1 is done at the _users_ side, not in the implementation.
This can (on very rare occasions) be of benefit to the optimizer in the
caller. Suppose the following are all true:
1) The operator+ is not inline.
2) The copy constructor does not modify its argument and the compiler can
tell (because it is default or inline).
3) No pointer or reference to the operator+ argument has "leaked" out of the
caller.
If these are all true, the compiler can be sure that the argument is not
modified and it might be able to optimize that knowledge (perhaps by leaving
a data member in a register across the call).
Note that passing an argument by const& does NOT mean the compiler of the
caller may assume that the argument isn't modified.
I'll admit that this combination of circumstances is unusual.
>The code shown above requires the so called "Named Return Value
>Optimization" to be properly optimized. It's beleived to be widely
>implemented but I'm unable to find it in EGCS.
What are you talking about? Consider this:
C operator+(const C& lhs, const C& rhs)
{
return C(lhs)+=rhs; // returning compound object
}
The standard requires a call to the copy ctor upon return!
A temp copy of C is created. Call it 'tmp1'
'tmp1' is changed in place by op+=
'tmp1' is copied into return value space
'tmp1' is destroyed
Note that there are two calls to the copy ctor.
It is entirely equivalent to this
C operator+(const C& lhs, const C& rhs)
{
C obj(lhs);
obj+=rhs;
return obj;
}
-----
The compiler may eliminate the call to the copy ctor upon return
by creating the temp object directly in the return space -- but
only when it is correct to do so. It doesn't matter whether you
use named objects or unnamed ones, as illustrated above. What
matters is whether you return pure objects or compound objects.
To do the optimization,
[X] all return points of a function return the same local object
actually this rule can be relaxed a little: we can return
different local objects, but the local objects must have
mutually exclusive scopes.
[X] the copy ctor for the local object type creates identical
copies and has no side effects. identical means bitwise
equality. so the copy ctor for dynamic arrays is not a
candidate, but the copy ctor for fixed sized arrays is.
[X] the dtor for the local object type has no side effects
[X] the dtors for other local objects don't alter the local
object to be returned (not sure about this one though)
-----
I have a question I don't know the answer to. In what order
are the objects copied and destroyed at the end of the
function?
MyClass func()
{
MyClass a,b,c;
return b; // b copied into return value space
}
order1: "c" destroyed, "b" copied, "b" destroyed, "a" destroyed
order2: "c" destroyed, "a" destroyed, "b" copied, "b" destroyed
--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------
> - when you implement assignment, always implement the complete
> protocol:
> - declare copy assignment as "T& T::operator=(const T&)"
> - never return const T&; although this would be nice since
> it prevents usage like "(a=b)=c", it would mean you
> couldn't portably put T objects into standard library
> containers, since these require that assignment returns
> a plain T& (Cline95: 212; Murray93: 32-33)
> [...]
I think the 'always' above should be stripped. Eg, my RefCount class
in the current GotW returns void. This was for a reason. We want to
highlight that class RefCount is a weird class. It's not intended to
be used as a normal object. More to the point, I don't want the user
to make an std::container or any container of RefCount objects. But
it is limited to this -- T::op= should return either T& or void, and
void only in the one in a million case. Kanze's suggestion has op=
return RefCount&, but in this context, I don't fully agree with his
return choice.
>mis-read the alternative version. If they were maintenance
>programmers, they would have wasted time and effort "fixing"
>your code. Bear in mind that the "code reader" or "maintenance
>programmer" is likely to be you after you have forgotten
>about the trick you were using.
So I guess the interface of a function is the most important
thing? What about classes with ref-counting that pass by value
for certain reasons, etc. I think there are more advanced
trick than this one is C++. But maybe we don't need more...
>Clarity and simplicity should be preferred unless there is a
>specific reason to use trickier code. I don't see how an unsymmetric
>interface to a symmetric operation can be considered clearer or
>simpler. The traditional example is simple and clearly correct.
Well, there is no question that the alternative is simpler. The real
question is (I think) if we should always prefer pass by ref (for
object) even if we need to modify it for some reason in the function.
void AnyFunction(const aType& aParameterThatICantModify)
{
// For my processing I need to modify the parameter so
aType aCopyIWillModifyInstead(aParameterThatICantModify);
// Don't use the parameter ! use the copy...
...
}
Or
void AnyFunction(aType aParameter)
{
// For my processing I need to modify the parameter, it's OK
...
}
If only the former is acceptable, then maybe C++ should have pass
by ref by default for object parameter (like Java). If not, it
may be tough to draw the line...
> > > >>>const C operator+(C c1, const C& c2)
> But it does. If you declare operator+ as asymmetric as the above,
> you'll find that its behaviour with regard to type conversions is no
> longer commutative.
For example?
--
--
Andrew Koenig
a...@research.att.com
http://www.research.att.com/info/ark
[J Kanze]
>There is one case where the call by value is better: if the caller is passing
>a temporary. For example, the expression a+b+c, which is
>
> operator+( operator+( a , b ) , c )
>
>When passing a reference, the compiler must build the return value into
>a temporary, and pass the reference to that. This temporary will then
>be copied. With pass by value, the compiler will probably construct the
>return value of the first call directly into argument, thus saving a copy.
Why will the temp be copied? Please explain the steps.
This is how I see it:
MyClass operator+(const MyClass& lhs, const MyClass& rhs);
// a+b+c;
(a+b) results in a temp 'tmp1'.
(tmp1+c) results in a temp 'tmp2'.
'tmp2' destroyed
'tmp1' destroyed
If operator+ does not invoke any calls to the copy ctor, then the
expression a+b+c results in no calls to the copy ctor. Contrast
this with pass by value:
MyClass operator+(MyClass lhs, const MyClass& rhs);
'a' copied into 'tmp1'
(tmp1+b) results in a temp 'tmp2'
(tmp2+c) results in a temp 'tmp3'
'tmp3' destroyed
'tmp2' destroyed
'tmp1' destroyed
Play with the attached program.
-----
[Valentin Bonnard]
>Well, there is reason to use your solution: efficiency.
>
>y = B() + x;
>
>If op+ takes a const B& and isn't inlined, the compiler
>won't see anything and will make a copy of the B() rvalue !
>
>
>That's of course silly.
>
>However, if op+ takes a B, even if the operator ins't
>inlined, the B() can be constructed in place (by merging
>B() and the function argument), saving a copy ctor call.
I don't buy this either.
MyClass operator+(const MyClass& lhs, const MyClass& rhs);
B() results in a temp 'tmp1'
'tmp1' passed to operator+ by reference, resulting in 'tmp2'
call to copy ctor only when 'tmp2' copied into 'y'
'tmp2' destroyed
'tmp1' destroyed
MyClass operator+(MyClass lhs, const MyClass& rhs);
B() results in a temp 'tmp1' -- result placed directly on stack
note that 'tmp1' is already on stack; operator+ called, resulting in 'tmp2'
call to copy ctor only when 'tmp2' copied into 'y'
'tmp2' destroyed
'tmp1' destroyed
Both methods are (almost) exactly the same! In particular, neither
method involves more calls to the copy ctor than is necessary.
The pass by const reference is negligibly slower because a pointer to
the object in question is passed. The stack pointer is incremented
by sizeof(MyClass*)+whatever bytes. Then 'lhs' is dereferenced to
locate the actual object 'lhs', 'rhs' is dereferenced similarly, and
operator+ does its job.
In pass by value, the object is already on the stack, so nothing needs
to be passed. The stack pointer is incremented by
sizeof(MyClass)+whatever bytes. To locate the real object 'lhs' we
just do a little arithmetic on the stack pointer. To locate the
object pointed to by 'rhs', we just dereference the pointer 'rhs', as
above. Then operator+ does its job.
So you see, the pass by value is more efficient in this case by one
or two cycles. Who cares about this anyway? The fact that we're
passing by reference in the first place suggests that we're dealing
with big objects, which suggests that operator+ is a big function
that does lots of stuff. The fact that operator+ is not inline is
another indication that operator+ is a big function that does lots
of stuff. So 99.999% of the time of the function operator+ is
spent in the actual adding, in the meat of the function (BTW, I'm
vegetarian :)). So what difference do the extra cycles make?
-----
// Play around with this program
// Note that the copy ctor and dtor have side effects
// This means they can never be optimized away
// It also means that no matter how smart your optimizer is, the results
// of this program will always be the same
#include <iostream>
class Type
{
private:
int num;
public:
Type(int num_) : num(num_)
{
std::cout << "Type::Type(int): " << *this << '\n';
}
Type(const Type& that) : num(that.num)
{
std::cout << "Type::Type(const Type&): " << *this << '\n';
}
Type& operator=(const Type&);
~Type()
{
std::cout << "Type::~Type(): " << *this << '\n';
}
friend inline Type operator+(const Type& lhs, const Type& rhs)
{
return Type(lhs.num+rhs.num);
}
friend inline std::ostream& operator<<(std::ostream& strm, const Type& t)
{
return strm << "Type(" << t.num << ')';
}
};
int main()
{
Type a(1), b(10), c(100);
a+b+c;
}
-----
Results using egcs 1.1 and KCC 3.3a:
Type::Type(int): Type(1)
Type::Type(int): Type(10)
Type::Type(int): Type(100)
Type::Type(int): Type(11)
Type::Type(int): Type(111)
Type::~Type(): Type(111)
Type::~Type(): Type(11)
Type::~Type(): Type(100)
Type::~Type(): Type(10)
Type::~Type(): Type(1)
--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
>> Which one you make pass by value, of course, doesn't matter for addition.
>But it does. If you declare operator+ as asymmetric as the above,
>you'll find that its behaviour with regard to type conversions is no
>longer commutative.
Interesting. I'm not sure if I can buy this. Give an example please.
I certainly agree with these sentiments, however passing by const & is
not the same as pass by value. In the later case optimisers can always
keep a cached value of the original argument, in the latter case the
cache may sometimes have to be flushed. (to see why, look at some of
the cases where programmers legitimately use const_cast) There is also
a possible problem with aliasing (no aliasing is possible in pass by
value, but is possible in pass by reference).
And then there is the issue of multi-threading.
None of the above should be relevant in this case but programmers should
be aware of the difference between pass by value and pass by const &.
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[...]
>
> // Play around with this program
> // Note that the copy ctor and dtor have side effects
> // This means they can never be optimized away
> // It also means that no matter how smart your optimizer is, the results
> // of this program will always be the same
No, you can't predict. See my other posting.
--
Gabriel Dos Reis | Centre de Mathématiques et de
dos...@cmla.ens-cachan.fr | Leurs Applications
Fax : (33) 01 47 40 21 69 | ENS de Cachan -- CNRS (URA 1611)
61, Avenue du Pdt Wilson, 94235 Cachan - Cedex
> >The code shown above requires the so called "Named Return Value
> >Optimization" to be properly optimized. It's beleived to be widely
> >implemented but I'm unable to find it in EGCS.
> I may be wrong (because I dont have the final standard) but I
> think every one of the implementation would benefit from
> this optimization.
I don't know. What is sure is most compilers tend to optimize unamed
objects more agressively than named objects. BTW, the Named Return Value
Optimization requires a copy constructor being provided explicitly.
> On 18 Sep 1998 15:54:46 -0400, Gabriel Dos Reis
>
> >The code shown above requires the so called "Named Return Value
> >Optimization" to be properly optimized. It's beleived to be widely
> >implemented but I'm unable to find it in EGCS.
>
> What are you talking about?
I was talking about return value optimization _and_ a compiler's
ability to optimize away a *named* return value.
> Consider this:
>
> C operator+(const C& lhs, const C& rhs)
> {
> return C(lhs)+=rhs; // returning compound object
> }
>
> The standard requires a call to the copy ctor upon return!
Sorry, this is not true. The Standard allows an implementation not to
call a copy constructor for a return value. It even made a special
rule for such cases. See below.
> A temp copy of C is created. Call it 'tmp1'
> 'tmp1' is changed in place by op+=
> 'tmp1' is copied into return value space
> 'tmp1' is destroyed
>
> Note that there are two calls to the copy ctor.
> It is entirely equivalent to this
>
> C operator+(const C& lhs, const C& rhs)
> {
> C obj(lhs);
> obj+=rhs;
> return obj;
> }
>
> -----
>
> The compiler may eliminate the call to the copy ctor upon return
> by creating the temp object directly in the return space -- but
> only when it is correct to do so.
When the semantic restrictions are met.
> It doesn't matter whether you
> use named objects or unnamed ones, as illustrated above. What
> matters is whether you return pure objects or compound objects.
> To do the optimization,
>
Yes. *In theory* it doesn't matter. In practice it does. It depends on
the compiler's agressiveness. That is why I wrote:
=> The code shown above requires the so called "Named Return Value
=> Optimization" to be properly optimized. It's beleived to be widely
=> implemented but I'm unable to find it in EGCS.
> [X] all return points of a function return the same local object
> actually this rule can be relaxed a little: we can return
> different local objects, but the local objects must have
> mutually exclusive scopes.
>
> [X] the copy ctor for the local object type creates identical
> copies and has no side effects. identical means bitwise
> equality. so the copy ctor for dynamic arrays is not a
> candidate, but the copy ctor for fixed sized arrays is.
>
> [X] the dtor for the local object type has no side effects
>
> [X] the dtors for other local objects don't alter the local
> object to be returned (not sure about this one though)
I disagree: the hypothetical side-effects in the ctor/dtor are
irrelevant.
Here is a quote from the Standard:
Copying class objects [12.8/15]
Whenever a temporary class object is copied using a copy
constructor, and this object and the copy have the same
cv-unqualified type, an implementation is permitted to treat
the original and the copy as two different ways of referring
to the same object and not perform a copy at all, even if the
class copy constructor or destructor have side effects. For a
function with a class return type, if the expression in the
return type statement is the name of a local object, and the
cv-unqualified type of the local object is the same as the
function return type, an implementation is permitted to omit
creating the temporary object to hold the function return
value, even if the class copy constructor or destructor has
side effects. In these cases, the object is destroyed at the
later of times when the original and the copy would have been
destroyed without the optimization.
The former optimization is the so called "unamed return value
optimization" and the later, as you have guessed, the "named return
value optimization".
You should definitely not rely on side effects in constructor or
destructor.
>>The traditional example is symmetrical in its parameters, but
>>the alternative is not. That alone causes readers of your code to
>>wonder why.
>If the alternative becomes an idiom, everyone would recognize it.
>But I never ask for that, I was trying to find out what made
>the other two "popular" and not this one... So you're right:
>it is because nobody uses it. But nobody uses it because it is
>not popular?!
In addition, treatment of actual arguments is not symmetrical.
The differences go beyond popularity, because program correctness
is involved.
You also seem to downgrade the importance of symmetry. Binary
addition is ordinarily considered symmetric. When the function
interface is also symmetric, readers of your code feel comfortable
that they understand what is going on. When the interface is not
symmetric, the careful reader must wonder wny, and spend time
understanding the reason for the assymetry -- or risk missing
an important feature of the code. You must ask what benefit is
gained in exchange for the extra time and effort you impose
on every future reader and maintainer of your code.
>The real
>question is (I think) if we should always prefer pass by ref (for
>object) even if we need to modify it for some reason in the function.
If a question contains "always" or "never", the answer is always
"no" and never "yes". :-)
>void AnyFunction(const aType& aParameterThatICantModify)
> {
> // For my processing I need to modify the parameter so
> aType aCopyIWillModifyInstead(aParameterThatICantModify);
> // Don't use the parameter ! use the copy...
> ...
> }
>Or
>void AnyFunction(aType aParameter)
> {
> // For my processing I need to modify the parameter, it's OK
> ...
> }
You choose reference passing if you want polymorphism or if you
want to avoid the overhead of copying.
If polymorphism is not an issue and copying is cheap, you can
reasonably choose to pass by value. For simple types, passing
by value is ordinarily preferred.
In your example, you need a copy whether copying is cheap or not.
Polymorphism affects program correctness, and so must have
first priority. If you want polymorphic behavior, you must
pass by reference, and you will also need a "clone" function
in the class.
If you don't care about polymorphism, you can't predict which, if
either, of the two alternatives above will be more efficient. I
don't think it matters which you choose.
Notice the difference from the operator+ example: There is only
one parameter, so symmetry considerations don't apply.
--
Steve Clamage, stephen...@sun.com
>I have a question I don't know the answer to. In what order
>are the objects copied and destroyed at the end of the
>function?
>MyClass func()
>{
> MyClass a,b,c;
> return b; // b copied into return value space
>}
>order1: "c" destroyed, "b" copied, "b" destroyed, "a" destroyed
>order2: "c" destroyed, "a" destroyed, "b" copied, "b" destroyed
The objects must be constructed in the order a, b, c.
They must be destroyed in reverse order of construction: c, b, a.
Order 2 is therefore not allowed.
Object b must be copied out to wherever the return value goes
before it is destroyed. The detailed order is unspecified.
Well that is, I think, an implementation detail.
>
>[X] the copy ctor for the local object type creates identical
> copies and has no side effects. identical means bitwise
> equality. so the copy ctor for dynamic arrays is not a
> candidate, but the copy ctor for fixed sized arrays is.
And exactly how do you propose that the compiler identify these cases
(it cannot see out of line copy ctors). Bitwise equality would be far
to stringent a requirement, and generally not verifiable (because
default copy ctors have to do memberwise copying)
>
>[X] the dtor for the local object type has no side effects
Likewise (it cannot see out of line dtors)
>
>[X] the dtors for other local objects don't alter the local
> object to be returned (not sure about this one though)
Why?
In fact the misnamed named return value optimisation permits compilers
to make a good number of assumptions not least that copy ctors and dtors
are semantically just what you might expect (copy and destruct). It was
trying to pin down what that last sentence means that lead to the
requirement for value parameters to be initialised by call to the
relevant copy ctor. The idea that we might actually lose all
opportunity to optimise the returns just sent a shudder through all
concerned. It simply was not an acceptable price.
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
OK. This is good to know. I still have some questions though.
It seems to me that the temporary they're talking about in the
above paragraph is yet another temporary, namely that which
results when we copy two temps in a row. I could be wrong,
though. Consider this program. For the moment, consider only
LINE2.
void func(const C&);
C operator+(const C& lhs, const C& rhs) { return C(lhs)+=rhs; }
C a,b;
C c(a+b); // LINE2
func(a+b); // LINE3
What you seem to be saying is that in LINE2, there will only be one
call to the copy ctor. That is
[X] 'a' copied into the return value space of the op+ function
the compiler ensures that the return value space is the same as 'c'
[X] this copy (or 'c') changed in place by the op+= function
This is not what two of my compilers do. They make two calls to the
copy ctor -- one inside the body of operator+ and one to copy the
result of operator+ into 'c'. Are you saying that they can indeed
make one copy?
As an aside, note that the above optimization is certainly valid and
useful, especially for numerical work, but I think it can only be
done if the copy ctor and dtor have no side effects. Right?
Now, here's my theory:
In LINE2, in the unoptimized case, there are 3 copies:
'a' copied to make a temp 'tmp1' [first copy]
'tmp1' changed in place by op+=
'tmp1' copied into return value space 'tmp2' [second copy]
'tmp2' copied into 'c' [third copy]
My undertanding of the quote from the standard is twofold:
[X] the second and third copies are conceptually identical
[X] either the second or third copy, but not both, can be eliminated
If someone asks which copy was optimized away -- the one in the
called func [copy two] or the one in the calling func [copy three]
-- the correct answer is probably: just one of them, but we don't
know which.
So after optimization, there are only two calls to the copy ctor.
Common sense tells me that this optimization should always be done
because the side effects seen should not depend on the optimization
level.
-----
>The former optimization is the so called "unamed return value
>optimization" and the later, as you have guessed, the "named return
>value optimization".
OK, but in reality there should be no duality between named and
unnamed objects.
>You should definitely not rely on side effects in constructor or
>destructor.
Interesting...
-----
Anyway, to J. Kanze and V. Bonnard, my apologies. The
operator+(value,const_reference) version is more efficient.
operator+(const_reference lhs,const_reference rhs) { return copy(lhs)+=rhs); }
d(a+b+c);
'a' copied inside body of op+ to make 'tmp1' (lives in called scope)
'tmp1' changed in place by op+=
'tmp1' copied into return value space 'tmp2' (lives in calling scope)
'tmp1' destroyed
'tmp2' copied in body of op+= to make 'tmp3' (lives in called scope)
'tmp3' changed in place by op+=
'tmp3' copied into return value space 'tmp4' (lives in calling scope)
'tmp3' destroyed
'tmp4' synonymous with 'd' (named/un-named optimization) 'tmp4' not destroyed
'tmp2' destroyed
operator+(value lhs,const_reference rhs) { return lhs+=rhs); }
d(a+b+c);
'a' copied to make 'tmp1' (lives in calling scope)
'tmp1' changed in place by op+=
'tmp1' copied into return value space 'tmp2' (lives in calling scope)
'tmp2' changed in place by op+=
'tmp2' copied into return value space 'tmp3' (lives in calling scope)
'tmp3' synonymous with 'd' (named/un-named optimization) 'tmp3' not destroyed
'tmp2' destroyed
'tmp1' destroyed
But operator constructors are still more efficient.
--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Well, this is what I've observed:
C func()
{
C out;
/* processing */
return out;
}
At the return, there is a call to the copy ctor to place the local
variable 'out' in the return space. Are you saying that the
compiler can create the variable 'out' directly in the return
space, thereby eliminating the call to the copy ctor and dtor of
the local 'out'? If this is so, then variable 'myc' will be
constructed in place in this example:
C myc(func());
Currently, on my compilers, there is one call to the copy ctor to
copy the local 'out' into the space for the object 'myc' (which
the compiler gaurantees is the same as the return space of function
func(...)).
-----
>>[X] all return points of a function return the same local object
>> actually this rule can be relaxed a little: we can return
>> different local objects, but the local objects must have
>> mutually exclusive scopes.
>
>Well that is, I think, an implementation detail.
Why is it an implementation detail? It is true, isn't it?
>>[X] the copy ctor for the local object type creates identical
>> copies and has no side effects. identical means bitwise
>> equality. so the copy ctor for dynamic arrays is not a
>> candidate, but the copy ctor for fixed sized arrays is.
>And exactly how do you propose that the compiler identify these cases
>(it cannot see out of line copy ctors). Bitwise equality would be far
>to stringent a requirement, and generally not verifiable (because
>default copy ctors have to do memberwise copying)
Bitwise equality is stringent, but I can't think of anything better.
This criterion of equality is applicable to fixed size arrays, but
not to dynamic arrays. After all, if 'v' is a dynamic array, how
can the compiler determine at compile time that "v" and "copy(v)" are
equivalent? The criterion for equality is not the pointers inside
'v' and 'copy(v)' are equal, but rather the objects pointed to by
these pointers are equal. How do we communicate this information to
the compiler?
Anyway, consider this copy ctor
template <typename T, int N>
inline TinyVec<T,N>::TinyVec(const TinyVec& that)
{
// d_vector has type T[N]
std::copy(that.d_vector,that.d_vector+N,this->d_vector);
}
The compiler looks inside the copy ctor and sees that the copy ctor
for TinyVec does indeed create identical copies and has no side
effects. Of course, this is so provided that typename 'T' is a
simple class, like double or complex<double>.
Inlining is an implementation detail. If the above ctor were not
inline, the compiler can still determine that 'v' and 'copy(v)'
are identical. It can do the optimization at link time, when the
ctor body for TinyVec(const TinyVec&) becomes available. Or it
can create a header-object file when parsing the file TinyVec.CPP.
This header-object file stores the fact that the copy ctor for
class TinyVec<T,N> creates identical copies with no side effects
whenever the copy ctor for T creates identical copies with no
side effects.
However, as a first step, we don't have to bother doing the
optimization for non-inline copy ctors. In short, for this
func:
TinyVec& operator+(const TinyVec& lhs, const TinyVec& rhs)
{
TinyVec out;
// out[i]=lhs[i]+rhs[i]; // for loop
return out;
}
TinyVec first, second;
TinyVec result=first+second;
Currently my compilers invoke one call to the copy ctor. They copy
the local variable 'out' into 'result'.
After the above optimization, the result of operator+ will be
constructed directly in 'result', so there are no calls to the copy
ctor! Because all return points of operarator+ return the same
object, and because copies for class TinyVec are identical, the
compiler can set things up so that the variable 'out', the
return space of operator+, and the variable 'result' are really
the same variable. So 'result' can be constructed in place.
This works whether op+ is inline or not. In any case, I'm not
sure if the optimization is worth it because the time saved is
actually not that much.
>>[X] the dtor for the local object type has no side effects
>Likewise (it cannot see out of line dtors)
See above.
>>[X] the dtors for other local objects don't alter the local
>> object to be returned (not sure about this one though)
>
>Why?
Nevermind, this is bullshit. Steve Clamage's answer to my other
question cleared this up.
>In fact the misnamed named return value optimisation permits compilers
>to make a good number of assumptions not least that copy ctors and dtors
>are semantically just what you might expect (copy and destruct). It was
>trying to pin down what that last sentence means that lead to the
>requirement for value parameters to be initialised by call to the
>relevant copy ctor. The idea that we might actually lose all
>opportunity to optimise the returns just sent a shudder through all
>concerned. It simply was not an acceptable price.
I think return value optimization refers to temps that are copied in
a row. So
C myobj(func());
involves two calls to the copy ctor. One to copy the return value of
func(...) into the return space, and another to copy this return
space into 'myobj'. Doesn't the return value optimization mean that
one of these copies can be eliminated?
But if func() returns a 'C' object through an operator constructor,
then there are no calls to the copy ctor at all. Eg, instead of
C func() { return C(1)+=C(2); } // one call to copy ctor
we do
C func() { return C(C(1),C(2),C::ADD()); } // no calls to copy ctor
Also, if the compiler can determine that copy(C) is identical to C
and has no side effects -- an in general, it will have to look in
the body of the copy ctor of class C to ascertain this -- then it
can also eliminate all extraneous copies.
jka...@otelo.ibmmail.com wrote:
> In article <6toj2i$ar1$1...@shell7.ba.best.com>,
> n...@nospam.cantrip.org (Nathan Myers) wrote:
>
> > If it's a good thing to pass c2 by "const&", then why not c1?
> > If it's a good thing to pass c1 by value, then why not c2?
>
> You have to make a copy of one. By declaring it by value, you let
> the user make the copy. You may even avoid the copy; if the argument
> is a temporary, the compiler will construct it immediately into the
> parameter.
>
> Which one you make pass by value, of course, doesn't matter for
addition.
> It does, however, for subtraction.
Is it so? If You consider following code:
C a,b,c,d, result;
...
result=a+b+c+d;
I've made test code with both versions and passing first parameter as
copy
requires 9 object constructions, passing second one requires 11 object
constructions. (tested with IBM's VAC++ 3.5 and MS VC++ 5.0). This may
change
of course, if some other compilers are smart enough to optimize this
difference out (or is it allowed by standard?).
Just curious.
>> Which one you make pass by value, of course, doesn't matter for
addition.
> But it does. If you declare operator+ as asymmetric as the above,
> you'll find that its behaviour with regard to type conversions is no
> longer commutative.
Given the original definition { C t = c1; return t + c2; }, there's no
difference at all. But it could certainly make a difference if you
were to dynamic_(down)cast the first argument for kind of dynamic
type-based overload resolution.
--
Alexandre Oliva
mailto:ol...@dcc.unicamp.br mailto:aol...@acm.org
http://www.dcc.unicamp.br/~oliva
Universidade Estadual de Campinas, SP, Brasil
However I believe that his explanation is very much based on a method
style of implementation (as manifested by CFront)
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
I am not sure that it is allowed to do that any more. Copy constructors
must be called to initialise value parameters. NO, I do not like it
either.
>>Clarity and simplicity should be preferred unless there is a
>>specific reason to use trickier code. I don't see how an unsymmetric
>>interface to a symmetric operation can be considered clearer or
>>simpler. The traditional example is simple and clearly correct.
Good. Furthermore, I don't see how the trickier code leads to better
optimizations. So the simpler version is also efficient, but I could
be wrong.
>I certainly agree with these sentiments, however passing by const & is
>not the same as pass by value. In the later case optimisers can always
>keep a cached value of the original argument, in the latter case the
>cache may sometimes have to be flushed. (to see why, look at some of
>the cases where programmers legitimately use const_cast)
I'm not familiar with this terminology -- "cached value",
"flush the cache", "original argument" (original relative to what?).
Can you please explain this more clearly, preferably with some
examples? The examples should illustrate how one method is better
than the other.
> There is also
>a possible problem with aliasing (no aliasing is possible in pass by
>value, but is possible in pass by reference).
Aliasing is possible in pass by value! This is because most objects
contain pointers within them. Eg, dynamic arrays.
void func(string a, const string b);
"a" and "b" might be the same string. Without doing a detailed
analysis of our reference counting mechanism, the compiler must
assume that changing "a" changes "b". We know that changing "a"
causes a unique copy of the string to be made, in which case "b"
doesn't really change.
Besides, what does aliasing have to do with any of this?!
>And then there is the issue of multi-threading.
Explain.
>None of the above should be relevant in this case but programmers
should
>be aware of the difference between pass by value and pass by const &.
My recommendation is as follows. If all of the following are true:
[X] the type has no dynamic memory
[X] copies are identical and have no side effects
[X] the dtor also has no side effects
[X] the called func is inline
If all these conditions are true, then regardless of how big the object
is, pass it by value. In this case, pass by value ensures that there
is no aliasing. Copies will always be eliminated whenever possible --
and this is most of the time. Calculations will be done inline, loops
will be merged.
Of course, today's compilers are not the advanced. They don't yet look
into the bodies' of the copy ctors of these 'value' objects. Hence
they can't see that copies are indeed identical and have no side
effects.
So until we the day when we have super smart optimizers, if the object
has sizeof>16, then we should always pass by const reference.
--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
It is in the nature of optimisation to be a QoI issue. The point behind
the rules for copy ctors is that they relax the requirement that the
compiler should be able to determine that the optimisation follows the
as-if rule. They are also absolute optimisations in that you cannot
require they be switched off (though, obviously, implementors can offer
you that choice).
Copy ctors can always be optimised away except as specifically required
by the standard. This makes the optimisation different from any others.
Note that a copy ctor can be optimised away even if the compiler can
determine that the dtor has side effects. In addition compilers are
free to call accessible copy ctors whenever they determine that they
need to generate a copy.
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[...]
>
> OK. This is good to know. I still have some questions though.
> It seems to me that the temporary they're talking about in the
> above paragraph is yet another temporary, namely that which
> results when we copy two temps in a row. I could be wrong,
> though. Consider this program. For the moment, consider only
> LINE2.
>
> void func(const C&);
> C operator+(const C& lhs, const C& rhs) { return C(lhs)+=rhs; }
> C a,b;
> C c(a+b); // LINE2
> func(a+b); // LINE3
>
>
> What you seem to be saying is that in LINE2, there will only be one
> call to the copy ctor. That is
No, I'm saying the compiler *is allowed* to compute/construct in c
thus resulting in one copy ctor call -- after it had checked that the
semantic restrictions apply, e.g. the copy ctor is accessible.
>
> [X] 'a' copied into the return value space of the op+ function
> the compiler ensures that the return value space is the same as 'c'
> [X] this copy (or 'c') changed in place by the op+= function
Exactly.
>
> This is not what two of my compilers do. They make two calls to the
> copy ctor -- one inside the body of operator+ and one to copy the
> result of operator+ into 'c'.
I would say your compilers aren't agressive, still they are standard
conforming in this case ;-)
> Are you saying that they can indeed
> make one copy?
>
Yes.
> As an aside, note that the above optimization is certainly valid and
> useful, especially for numerical work, but I think it can only be
> done if the copy ctor and dtor have no side effects. Right?
The Standard is quite clear about this issue. An implementation is
permitted to elide a copy ctor call in return value construction even
if the copy ctor has side effects.
>
>
> Now, here's my theory:
>
> In LINE2, in the unoptimized case, there are 3 copies:
>
> 'a' copied to make a temp 'tmp1' [first copy]
> 'tmp1' changed in place by op+=
> 'tmp1' copied into return value space 'tmp2' [second copy]
> 'tmp2' copied into 'c' [third copy]
>
This therory is correct according to the standard.
> My undertanding of the quote from the standard is twofold:
>
> [X] the second and third copies are conceptually identical
> [X] either the second or third copy, but not both, can be eliminated
>
My understanding of the standard is that a conforming implementation
may consider tmp1 and tmp2 as identical.
> If someone asks which copy was optimized away -- the one in the
> called func [copy two] or the one in the calling func [copy three]
> -- the correct answer is probably: just one of them, but we don't
> know which.
>
> So after optimization, there are only two calls to the copy ctor.
There could be only one in a conforming implementation.
> Common sense tells me that this optimization should always be done
> because the side effects seen should not depend on the optimization
> level.
>
Common sense tells me that one should not rely one side effects in
constructors.
> -----
>
> >The former optimization is the so called "unamed return value
> >optimization" and the later, as you have guessed, the "named return
> >value optimization".
>
> OK, but in reality there should be no duality between named and
> unnamed objects.
The named object is the one you give a name -- that implies you can
reference the name latter. The unnamed one's management is entirely
left at the discretion of the implementation. Conceptually and
practically there is a difference.
--
Gabriel Dos Reis | Centre de Mathématiques et de
dos...@cmla.ens-cachan.fr | Leurs Applications
Fax : (33) 01 47 40 21 69 | ENS de Cachan -- CNRS (URA 1611)
61, Avenue du Pdt Wilson, 94235 Cachan - Cedex
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
>Copy ctors can always be optimised away except as specifically required
>by the standard. This makes the optimisation different from any others.
>Note that a copy ctor can be optimised away even if the compiler can
>determine that the dtor has side effects. In addition compilers are
>free to call accessible copy ctors whenever they determine that they
>need to generate a copy.
OK. But first of all, if eliminating a copy does not change the
observable behaviour of the program, the elimination can always be
done, regardless of what the standard says! This means that side
effects in the copy ctors and dtors do matter.
For example, when you return a local object, there must be a call
to the copy ctor. But what's to stop the compiler from creating
the local object directly in the return value space, thereby
eliminating a call to the copy ctor and dtor?
--
Let's make this more concrete. I'm getting confused in the abstract
language. Please tell me how many copy ctors are called for the
following during LINE1?
T operator+(const T& lhs, const T& rhs) { return T(lhs)+=rhs; }
T result(a+b+c); // LINE1
Is it: 5, 4, 3, 2, 1, or 0? (Show what copies happen.)
My compilers do 4 copies.
Without any optimizations, there should be 5 copies.
Also, my claim is that if the copy ctors and dtors have no side
effects, then 2 and 0 copies are possible too (0 if 'result' if
everything is inline).
--
And how many copy ctors are called for the following during LINE1?
T operator+(T lhs, const T& rhs) { return lhs+=rhs; }
T result(a+b+c); // LINE1
Is it: 4, or 3
My compilers do 3 copies.
void foo(int);
void bar(int const &);
void fn(){
int a, b, c;
cin >> a >> b;
c = a+b;
foo(a);
cout << (a+b); // can use the value already calculated because foo
// CANNOT change the value of a
bar(b);
cout << (a+b); // unfortunately the compiler cannot be sure you did not
// change b, SHOULD NOT is not the same as CANNOT
}
If you think that the const qualification is enough to allow the
optimisation (I do not) then there are still problems when you use a
user defined type that includes pointers/references.
There is also a difference in the body of the called functions. When a
parameter is passed by value the compiler knows it has the only instance
and can decide how much it can do entirely in registers.
>
>> There is also
>>a possible problem with aliasing (no aliasing is possible in pass by
>>value, but is possible in pass by reference).
>
>Aliasing is possible in pass by value! This is because most objects
>contain pointers within them. Eg, dynamic arrays.
Most objects? Well you know your own code. Aliasing only happens when
the type does not own the object pointed to, that is far less common.
>
>void func(string a, const string b);
>
>"a" and "b" might be the same string. Without doing a detailed
>analysis of our reference counting mechanism, the compiler must
>assume that changing "a" changes "b". We know that changing "a"
>causes a unique copy of the string to be made, in which case "b"
>doesn't really change.
NO the compiler does not know that you will not write:
string a("stupid");
func(a,a)
In this case you have passed by value so it knows that the two
parameters are distinct. But in the case:
void gn(string & a, string const & b);
it has to presume that a and b might actually be the same object. That
is the aliasing problem.
>
>Besides, what does aliasing have to do with any of this?!
See above.
>
>
>>And then there is the issue of multi-threading.
>
>Explain.
Oh come now. If you pass by value you do not need a lock on the
argument throughout the execution of the function. If you pass by any
kind of reference or pointer you do. Multi-threading has some really
evil implications and needs a somewhat different programming style.
>
>
>>None of the above should be relevant in this case but programmers
>should
>>be aware of the difference between pass by value and pass by const &.
>
>My recommendation is as follows. If all of the following are true:
>
And my recommendation is that optimisation by programmers is nearly
always a bad idea. If you must do it, wait till you are certain you
need to (and that will be rare). However that is not the same as
choosing good algorithms.
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Right. Since the proposed alternative builds the copying (and possible
slicing) of the first argument right into the signature of the operator,
it
constrains the implementation. The conventional signature, in contrast,
allows
the implementation of the operator more freedom in choosing how to
perform the
copy. The copy, as you point out, may involve resolving the dynamic
type of
the first or second argument.
---
Roger L. Cauvin
rca...@homemail.com
http://members.forfree.at/~rcauvin/
Senior Technologist
ObjectSpace
I don't know. However, the proposed alternative has definite
disadvantages.
By building the copying (and slicing) of the first parameter right into
the
signature of the operator, it constrains the implementation.
---
I understand that the C++ standard requires this (unless it's changed
from the draft standard) but I don't understand _why_ STL requires
T& T::operator=(const T&)
and does not allow a class to only implement:
const T& T::operator(const T&)
or for some classes:
void T::operator(const T&)
The possible reasons I could find for the requirement are:
*The requirements on operator= were most easily expressed in this way.
*Some STL operations must send 'operator=' as a parameter to another
function and this requires one signature and being able to put
traditional types in STL containers give T& operator=(const T&)
*An implementation of STL must explicitly use the (a=b)=c idiom to
meet some of the STL requirements.
None of these seem convincing, so what's the real reason?
--
// Home page http://www.dna.lth.se/home/Hans_Olsson/
// Email To..Hans.Olsson@dna.lth.se [Please no junk e-mail]
[Attribution added] Gabriel Dos_Reis wrote:
: >Copying class objects [12.8/15]
[snip]
: OK. This is good to know.
Agreed, and it is nice to see the final wording.
: I still have some questions though.
: It seems to me that the temporary they're talking about in the
: above paragraph is yet another temporary, namely that which
: results when we copy two temps in a row. I could be wrong,
: though.
This thing has been debated many times. The quoted paragraph says
that the implementation is allowed to remove temporaries in specific
cases without considering side effects of copy-ctor and dtor. It
explicitely permits violation of the as-if rule. I can write a
conforming program which detects whether or not the optimization is
implemented. Since the optimization is both valuable and dangerous,
it is a quality of implementation issue. Egcs does not implement
NRVO, but there is an extension which you may use to see what happens.
The only implementation which I have been told implements it is HP and
it must be enabled by a commandline switch.
: Consider this program. For the moment, consider only
: LINE2.
: void func(const C&);
: C operator+(const C& lhs, const C& rhs) { return C(lhs)+=rhs; }
: C a,b;
: C c(a+b); // LINE2
: func(a+b); // LINE3
As you said, at line2, the temporary return value of operator+ is
removed by considering it another name for c. Unfortunately, you have
written operator+ in a manner which requires a copy. It does not
return the modified C(lhs), it returns whatever operator+= returns.
There is nothing in the above quote or anywhere else in the standard
that allows an implementation to assume that operator+= returns *this.
C& operator+= (C const&) {
static C junk;
// do the work on *this
return junk;
}
Let's rewrite it to allow NRVO.
C operator+ (C const& lhs, C const& rhs) {
C ret(lhs);
ret += rhs;
return ret;
}
The NRVO removes the local ret copying lhs into the return value (c in
the caller) and modifies it in place. The HP compiler with NRVO
enabled only performs one copy. Since you have egcs, here is the
extension which gives the same performance.
C operator+ (C const& lhs, C const& rhs) return ret(lhs) {
ret += rhs;
return ret;
}
: >You should definitely not rely on side effects in constructor or
: >destructor.
It may be frightening, you may not like it, but it is the standard.
Enjoy,
John
: What is sure is most compilers tend to optimize unamed
: objects more agressively than named objects. BTW, the Named Return
Value
: Optimization requires a copy constructor being provided explicitly.
Is that from the standard? Or, an observation on the switch used by
cfront? I didn't see it in your standard quote elsewhere in this
thread. I did find an example where egcs removed a copy when I
supplied the ctor; however, that was not NRVO. It does make sense
that removing a trivial copy might not be worth the bother, but I
don't think it is a standard requirement.
This question has generated some interesting discussion. Here is a
partial answer.
> Suppose I want to implement operator+ from operator+=.
> A classic implementation would be:
>
> const C operator+(const C& c1, const C& c2)
> {
> C c(c1);
> c+= c2;
> return c;
> }
> But what about this:
>
> const C operator+(C c1, const C& c2)
> {
> return c1+= c2;
> }
>
> I think it is simpler and it demands less operations
> than the other methods. But I don't seem to find this
> style anywhere. Maybe there is a problem with inlining
> it or optimizing it. What about making a template out
> of it? Any idea?
They are equally templatizable and inlinable.
There are two copies involved here. Typical implementation of return
by value is a pointer to uninitialized space for the return value
which is copy constructed by the function. The const part only
determines use of the result at the call site and is ignored here.
The two versions become:
void operator+(void* ret, const C& c1, const C& c2)
{
C c(c1); // copy 1
c+= c2;
new (ret) C(c); // copy 2
}
void operator+(void* ret, C c1, const C& c2)
// ^^ copy 1 at call site
{
new (ret) C(c1+= c2); // copy 2
}
Now apply the return value optimization to the first version by
removing the local "c".
void operator+(void* ret, const C& c1, const C& c2)
{
C* retPtr(new (ret) C(c1)); // only copy
*retPtr += c2;
}
One problem with the second version is that it requires the assumption
that C::operator+= returns *this. We can solve this problem easily.
void operator+(void* ret, C c1, const C& c2)
// ^^ copy 1 at call site
{
c1 += c2;
new (ret) C(c1); // copy 2
}
Now it is clear locally that the return value is copy initialized by
the modified c1. It is also clear that c1 could be removed by
initializing the return value by whatever c1 used. But, those two
things are in different places; so, we still get two copies.
Michel Michaud mic...@removethis.mail2.cstjean.qc.ca
http://www3.sympatico.ca/michel.michaud
It must call at least 1 and I think 2 (assuming a,b and c are of type T)
because it has to construct result (and it has to construct the
temporary to be copied, but I would be happy if it managed with just 1).
Other wise it is free to call as many as it likes (and one fewer dtors).
You simply must not make assumptions about copy ctors being called
except where they are explicitly required. I think you also have to
distinguish between the memory used and the constructors called.
>
>--
>
>And how many copy ctors are called for the following during LINE1?
>
>T operator+(T lhs, const T& rhs) { return lhs+=rhs; }
>T result(a+b+c); // LINE1
>
>Is it: 4, or 3
>My compilers do 3 copies.
Three, one to construct result and one each to pass parameters by value
(required by the final version of the standard)
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
>: C operator+(const C& lhs, const C& rhs) { return C(lhs)+=rhs; }
>C operator+ (C const& lhs, C const& rhs) {
> C ret(lhs);
> ret += rhs;
> return ret;
> }
What you say makes sense. Formerly, I thought that the optimization
could only be done if the compiler knew that the dtors had no side
effects. So is it this?
// no optimization
void operator+(void* ret, const C& lhs, const C& rhs)
{
C temp(lhs); // copy
temp+=rhs;
new (ret) C(temp); // copy
} // count: copy==2 , dtor==1
// optimization level 1
void operator+(void* ret, const C& lhs, const C& rhs)
{
new (ret) C(lhs); // copy (calling func gaurantees ret!=&lhs)
(*ret)+=rhs;
} // count: copy==1 , dtor==0
// optimization level 2
void operator+(void* ret, const C& lhs, const C& rhs)
{
if (ret!=&lhs) new (ret) C(lhs); // maybe copy
(*ret)+=rhs;
} // count: copy=={0,1} , dtor==0
C result(a+b+c); // 4 copies in no optimization
C result(a+b+c); // 2 copies in optimization level 1
C result(a+b+c); // 1 copy in optimization level 2
However, if you use operator ctors, you can have zero copies,
regardless of whether the named return value optimization was
done or not.
// original
C operator+(void* ret, const C& lhs, const C& rhs)
{
new (ret) C(lhs,rhs,C::ADD()); // there is struct C::ADD;
} // count: copy==0 , dtor==0
In this scheme, both operator+ and operator+= are implemented in
terms of another func, namely the operator constructor. Actually,
the operator ctor should call another private func because ctors
cannot be called on an already constructed object (ie, in order to
implement op+=).
Finally, everything so far can be used as follows:
// original
C func(bool cond, const C& lhs, const C& rhs)
{
if (cond==false) return C(); // return zero object
else return lhs+rhs;
}
// after optimization
void func(void* ret, bool cond, const C& lhs, const C& rhs)
{
if (cond) new (ret) C();
else operator+(ret,lhs,rhs);
} // count: ctor=={0,1} , dtor=0
>It may be frightening, you may not like it, but it is the standard.
What are some of the real dangers that could arise?
--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Could you please indicate where in the standard you found this. It
seems in contradiction with 12.8/15:
Whenever a temporary class object is copied using a copy
constructor, and this object and the copy have the same
cv-unqualified type, an implementation is permitted to treat the
original and the copy as two different ways of referring to the same
object and not perform a copy at all, even if the class copy
constructor or destructor have side effects. For a function with a
class return type, if the expression in the return statement is the
name of a local object, and the cv- unqualified type of the local
object is the same as the function return type, an implementation is
permitted to omit creating the temporary object to hold the function
return value, even if the class copy constructor or destructor has
side effects.
(I believe that this is the rule referred to by "named return value
optimization", and thus what the original poster was talking about.)
> -----
>
> I have a question I don't know the answer to. In what order
> are the objects copied and destroyed at the end of the
> function?
>
> MyClass func()
> {
> MyClass a,b,c;
> return b; // b copied into return value space
> }
>
> order1: "c" destroyed, "b" copied, "b" destroyed, "a" destroyed
> order2: "c" destroyed, "a" destroyed, "b" copied, "b" destroyed
The copy takes place before the end of scope, so the order is "b"
copied, "c" destroyed, "b" destroyed, "a" destroyed.
--
James Kanze +33 (0)1 39 23 84 71 mailto: ka...@gabi-soft.fr
+49 (0)69 66 45 33 10 mailto: jka...@otelo.ibmmail.com
GABI Software, 22 rue Jacques-Lemercier, 78000 Versailles, France
Conseils en informatique orientée objet --
-- Beratung in objektorientierter Datenverarbeitung
-----== Posted via Deja News, The Leader in Internet Discussion ==-----
http://www.dejanews.com/rg_mkgrp.xp Create Your Own Free Member Forum
Not really. I suspect that it is actually quite frequent.
However, much less is really needed. I think an optimization is in fact
possible any time the argument is not an lvalue. If I understand section
12.8/15 correctly, the compiler is always allowed to "unify" the temporary
and the argument itself, that is, to construct the temporary directly into
the argument, rather than to construct it separately and then copy it.
I'm really following up to Steve Clamage's posting, which I missed.
I agree totally with Steve's general comment. In this case, however:
1. Pass by value and pass by const reference are logically quite similar,
at least if the programmer isn't lying about the const. The use of the
two functions is almost identical. So the lack of symmetry is more
apparent than real.
2. There is a specific reason to use trickier code: the compiler may be
able to optimize it better. In general, I think too much importance is
given to performance, but this idiom principally concerns mathematical
programming, which is the one domain where performance probably is usually
an issue.
|> [J Kanze]
|> >There is one case where the call by value is better: if the caller is
passing
|> >a temporary. For example, the expression a+b+c, which is
|> >
|> > operator+( operator+( a , b ) , c )
|> >
|> >When passing a reference, the compiler must build the return value into
|> >a temporary, and pass the reference to that. This temporary will then
|> >be copied. With pass by value, the compiler will probably construct the
|> >return value of the first call directly into argument, thus saving a copy.
|> Why will the temp be copied? Please explain the steps.
|> This is how I see it:
|> MyClass operator+(const MyClass& lhs, const MyClass& rhs);
|> // a+b+c;
|> (a+b) results in a temp 'tmp1'.
|> (tmp1+c) results in a temp 'tmp2'.
|> 'tmp2' destroyed
|> 'tmp1' destroyed
|> If operator+ does not invoke any calls to the copy ctor, then the
|> expression a+b+c results in no calls to the copy ctor. Contrast
|> this with pass by value:
You seem to have missed the point of the idiom. Operator+ requires an
instance that it can modify. If passed a const reference, it must copy
it first; if passed a value, no copy is needed in the operator+ function
itself.
My example wasn't perhaps as well chosen as it could have been, since in
order to save the copy, the compiler also has to use named return value
optimization. With this optimization, however, the compiler will
construct the return value directly in the place of the argument; no
copy will be needed. In this case, the situation is exactly the same as
you explain for references, except that there is no need for a copy in
the function, either. The results should be a single temporary:
a copied into lhs argument..
operator+ called, no copy, results in the same lhs argument
which is, in fact, the first arg for the second call to operator+
An easier example to understand would be the case where the first
operand was the return value of a normal function, or simply a
constructor.
|> MyClass operator+(MyClass lhs, const MyClass& rhs);
|> 'a' copied into 'tmp1'
|> (tmp1+b) results in a temp 'tmp2'
The whole idea here is that the results can be in the same temp,
according to section 12.8/15 of the standard.
|> (tmp2+c) results in a temp 'tmp3'
|> 'tmp3' destroyed
|> 'tmp2' destroyed
|> 'tmp1' destroyed
|> Play with the attached program.
|> -----
|> [Valentin Bonnard]
|> >Well, there is reason to use your solution: efficiency.
|> >
|> >y = B() + x;
|> >
|> >If op+ takes a const B& and isn't inlined, the compiler
|> >won't see anything and will make a copy of the B() rvalue !
|> >
|> >
|> >That's of course silly.
|> >
|> >However, if op+ takes a B, even if the operator ins't
|> >inlined, the B() can be constructed in place (by merging
|> >B() and the function argument), saving a copy ctor call.
|> I don't buy this either.
And yet, the example is clearer than mine.
|> MyClass operator+(const MyClass& lhs, const MyClass& rhs);
|> B() results in a temp 'tmp1'
|> 'tmp1' passed to operator+ by reference, resulting in 'tmp2'
No temp is needed to pass tmp1. On the other hand, the code for
operator+ must copy to an internal variable, using the copy constructor.
|> call to copy ctor only when 'tmp2' copied into 'y'
Using named return value optimization, this could potentially be
optimized out as well.
|> 'tmp2' destroyed
|> 'tmp1' destroyed
With named return value optimization, we can reduce it to a single temp,
without, two copies are required.
The full sequence is:
B() constructs in tmp1.
operator+ called, with const reference to tmp1.
operator+ copies its first argument into a temporary, either named
or unnamed. (We'll call it tmp2.)
operator+ returns tmp2. If named return value optimization is in
effect, tmp2 is its return value, otherwise, an other temp is
required, and tmp2 is destructed after the copy.
The return value is assigned (not copied) to y, and the return value
variable (tmp2 with named return value optimization) is
destructed.
In all, two temps with named return value optimization, three without.
|> MyClass operator+(MyClass lhs, const MyClass& rhs);
|> B() results in a temp 'tmp1' -- result placed directly on stack
Result placed directly in argument, which is on the stack.
|> note that 'tmp1' is already on stack; operator+ called, resulting in 'tmp2'
|> call to copy ctor only when 'tmp2' copied into 'y'
|> 'tmp2' destroyed
|> 'tmp1' destroyed
As above:
B() constructs in tmp1, which is unified with lhs (the argument).
operator+ operates directly on lhs, and returns it.
If named return value optimization is in effect, it is possible that
the lhs is unified with the return value, so no temp is needed.
The return value is assigned to y.
In all, with named return value optimization, only one temp is used,
without two. In both cases, there is one less temp with pass by value.
[...]
|> -----
|> // Play around with this program
|> // Note that the copy ctor and dtor have side effects
|> // This means they can never be optimized away
According to 12.8/15, copy constructors and destructors can be optimized
away even if they have side effects.
|> // It also means that no matter how smart your optimizer is, the results
|> // of this program will always be the same
The code you posted is not very typical, in that there is no operator+=.
The "classical" idiom involves implementing operator+ in terms of
operator+=, e.g.:
#include <iostream>
class Type
{
private:
int num;
public:
Type(int num_) : num(num_)
{
std::cout << "Type::Type(int): " << *this << '\n';
}
Type(const Type& that) : num(that.num)
{
std::cout << "Type::Type(const Type&): " << *this << '\n';
}
Type& operator=(const Type&);
~Type()
{
std::cout << "Type::~Type(): " << *this << '\n';
}
Type& operator+=( Type const& other )
{
num += other.num ;
return *this ;
}
friend inline std::ostream& operator<<(std::ostream& strm, const Type&
t)
{
return strm << "Type(" << t.num << ')';
}
};
inline Type operator+(const Type& lhs, const Type& rhs)
{
return Type( lhs ) += rhs;
}
// inline Type operator+(Type lhs, const Type& rhs)
// {
// return lhs += rhs;
// }
int main()
{
Type a(1), b(10), c(100);
a+b+c;
return 0 ;
}
A quick check shows that at least on VC++, there is one less temporary
using pass by value.
Could you explain why? One of the motivations behind my RefCntPtr is
that I can use it safely in containers.
We can see that I could have written:
T result(a);
result += b;
result += c;
which requires only one copy constructor however when I write:
T result(a+b+c);
There is the problem of a sequence point after the evaluation of the
argument passed to the copy ctor (for create result). I suspect that
means that it must create at least one temporary (and that the ctor
better have a const & parameter and not a plain reference one.
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Is this not specified? It sounds dangerous to me. I would have thought
that copying b was part of the return statement, and that the destructors
would only be called after the return statement was finished.
--
James Kanze +33 (0)1 39 23 84 71 mailto: ka...@gabi-soft.fr
+49 (0)69 66 45 33 10 mailto: jka...@otelo.ibmmail.com
GABI Software, 22 rue Jacques-Lemercier, 78000 Versailles, France
Conseils en informatique orientée objet --
-- Beratung in objektorientierter Datenverarbeitung
-----== Posted via Deja News, The Leader in Internet Discussion ==-----
http://www.dejanews.com/rg_mkgrp.xp Create Your Own Free Member Forum
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
On 21 Sep 1998, Francis Glassborow wrote:
> In article <slrn700cr2....@localhost.localdomain>, Siemel Naran
> <sbn...@localhost.localdomain> writes
> >Let's make this more concrete. I'm getting confused in the abstract
> >language. Please tell me how many copy ctors are called for the
> >following during LINE1?
> >
> >T operator+(const T& lhs, const T& rhs) { return T(lhs)+=rhs; }
> >T result(a+b+c); // LINE1
> >
> >Is it: 5, 4, 3, 2, 1, or 0? (Show what copies happen.)
> >My compilers do 4 copies.
> >
> >Without any optimizations, there should be 5 copies.
> >
> >Also, my claim is that if the copy ctors and dtors have no side
> >effects, then 2 and 0 copies are possible too (0 if 'result' if
> >everything is inline).
>
> It must call at least 1 and I think 2 (assuming a,b and c are of type T)
> because it has to construct result (and it has to construct the
> temporary to be copied, but I would be happy if it managed with just 1).
> Other wise it is free to call as many as it likes (and one fewer dtors).
I think that we are talking about 12.8/15 posted elswhere in this thread
which allows violation of the as-if rule. Your counts seem to be off
because the return statement does not name a local variable. The
function
returns whatever operator+= returns. If we rewrite the function as:
{ T local(lhs); lhs += rhs; return local; }
I agree with everything that you said.
> You simply must not make assumptions about copy ctors being called
> except where they are explicitly required. I think you also have to
> distinguish between the memory used and the constructors called.
Amen!
> >And how many copy ctors are called for the following during LINE1?
> >
> >T operator+(T lhs, const T& rhs) { return lhs+=rhs; }
> >T result(a+b+c); // LINE1
> >
> >Is it: 4, or 3
> >My compilers do 3 copies.
> Three, one to construct result and one each to pass parameters by value
> (required by the final version of the standard)
Same problem here. Rewrite as:
{ lhs += rhs; return lhs; }
Now the return statement names something which is effectively a local
variable, but I do not know if it qualifies. Not considering the
implementation details of how to remove the temporary return value,
is it allowed to do so? This is a standard question which belongs
in csc++; however, practictioners also need to know how to make
this optimization possible and/or prevent it.
John
[J Kanze]
>With named return value optimization, we can reduce it to a single temp,
>without, two copies are required.
Thanks to this and the posts from a few other people, I understand this
point now. But couldn't you have 0 calls to the copy ctor?
B() creates its result in 'tmp1'
'tmp1' does not need to be copied into 'tmp2'
'tmp1' changed in place by op+=
compiler ensures that 'tmp1' is same as 'y'
nothing is destroyed
If op+ is inline, this should happen. If op+ is not inline, this
could still happen, but with more work from the compiler.
[J Kanze]
>The code you posted is not very typical, in that there is no operator+=.
>The "classical" idiom involves implementing operator+ in terms of
>operator+=, e.g.:
I like operator ctors. The important thing to me is that they seem
natural. They aren't fancy things that make programs faster but
more tricky to understand, more difficult to maintain. Also,
operator ctors work whether the return value optimization is applied
or not. They also merge for loops. However, if the class doesn't
have some operator ctor, that's too bad -- you can't reopen a
class like a namespace and add new funcs to it. Of course, you
could derive from the class and add whatever funcs you wanted.
But this may not always be appropriate.
--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
>Could you please indicate where in the standard you found this. It
>seems in contradiction with 12.8/15:
Oops. That was my common sense talking. I guess common sense didn't
serve me well here :(.
Assuming that the various op= have been provided by member functions and
that the corresponding functions are essentially forwarding functions to
these, what does the standard allow in the following:
T a, b, c, d;
// initialisations
T res1((a+b)*c-d);
T res2;
res2 = (a+b)*c-d;
The second of these can be unrolled by hand to:
res2=a;
res2+=b;
res2*=c;
res2-=d;
The first could have been written as:
T res1(a);
res1 += b;
res1 *= c;
res1 -= d;
In all but the most pathological cases the unrolled versions have the
same end result though the as-if rule does not apply because of the
potential for copy ctor and dtor behaviour being detectable. I think
res2 unrolling will always be fine as long as none of the pure op
functions use pass be value. The problem with the res1 unrolling is
that it removes a sequence point (though it introduces a lot of others);
I have a strong distaste for hand optimisation but it seems that here we
currently have little choice until cmpilers become much more aggressive,
and if we are to preserve potential for standard conforming aggressive
optimisation we must be very careful about use of value parameters.
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
This leads to an interesting thought:
Assume the following code:
void f()
{
MyClass a;
MyClass b(a);
MyClass c(b);
Modify(c);
assert(b!=c);
}
Here two copies are made, and therefore two copy constructor calls
must be done. Everything is Ok.
Now change it slightly:
void f()
{
MyClass a;
MyClass const& b (MyClass(a));
// creates a temporary and binds it to reference b
MyClass c(b); // copies the temporary
Modify(c);
assert(b!=c);
}
Now, since b is bound to a temporary, constructing c means copying
a temp. Therefore the copy may be omitted, making c in effect a
non-const reference to the same temporary b references. Now if
c is modified, b should get the same modification, and the assertion
should fail.
Is that correct, or is there anything I got wrong? (I really hope
I got it wrong!)
If op+ is compiled separately (*really* separately, that is without
the linker being able to change things or (re-)compile; say with
dynamic linking), then the compiler doesn't know if the reference
references a temporary or an actual named object. In the latter
case, the compiler *must* use a temporary (since otherwise it
would change its argument), and therefore the only way to be correct
in any case is to create that temporary.
(Actually, this is not completely correct: The compiler could
generate _two_ versions of the function - one for use with named
variables and one for use with temporaries - and extend the
overloading mechanism to deal with the temp/no-temp distinction
transparently)
[...]
Since _every_ copy construction from temporary may be optimized away
according to the Standard part quoted in the thread [12.8/15], this
is especially true, if the value copied-to is a value argument.
It's different if the source is not a temporary, of course.
[...]
>
> However, much less is really needed. I think an optimization is in fact
> possible any time the argument is not an lvalue. If I understand section
> 12.8/15 correctly, the compiler is always allowed to "unify" the temporary
> and the argument itself, that is, to construct the temporary directly into
> the argument, rather than to construct it separately and then copy it.
>
Yes. I'am curious to know how many modern compilers implement this
optimization. Someone told that HP's compiler does.
--
Gabriel Dos Reis | Centre de Mathématiques et de
dos...@cmla.ens-cachan.fr | Leurs Applications
Fax : (33) 01 47 40 21 69 | ENS de Cachan -- CNRS (URA 1611)
61, Avenue du Pdt Wilson, 94235 Cachan - Cedex
>Is this not specified? It sounds dangerous to me. I would have thought
>that copying b was part of the return statement, and that the destructors
>would only be called after the return statement was finished.
No. If the return value optimization is in effect, then 'b' will be
creating directly in the return space. The local 'a' and 'c' will
be destroyed in the usual order.
--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
> Gabriel Dos_Reis <gdos...@sophia.inria.fr> wrote:
>
>
> : What is sure is most compilers tend to optimize unamed
> : objects more agressively than named objects. BTW, the Named Return
> Value
> : Optimization requires a copy constructor being provided explicitly.
>
> Is that from the standard? Or, an observation on the switch used by
> cfront? I didn't see it in your standard quote elsewhere in this
> thread. I did find an example where egcs removed a copy when I
> supplied the ctor; however, that was not NRVO. It does make sense
> that removing a trivial copy might not be worth the bother, but I
> don't think it is a standard requirement.
>
I must confess that my earlier observation (about the explicit
definition of copy ctor in NRVO) is not a requirement from the
Standard -- or at least I cannot find an explicit statement -- but a
mere practical observation. Sorry for the confusion.
--
Gabriel Dos Reis | Centre de Mathématiques et de
dos...@cmla.ens-cachan.fr | Leurs Applications
Fax : (33) 01 47 40 21 69 | ENS de Cachan -- CNRS (URA 1611)
61, Avenue du Pdt Wilson, 94235 Cachan - Cedex
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
It may not elide a copy constructor unless it can ascertain that the
original is not subsequently used (except for dtor). Used is defined in
the standard. This is a simplification (we took hours over trying to
get the technical wording right)
Francis Glassborow Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
On 22 Sep 1998, Gabriel Dos_Reis wrote:
> jka...@otelo.ibmmail.com writes:
>
> [...]
>
> >
> > However, much less is really needed. I think an optimization is in fact
> > possible any time the argument is not an lvalue. If I understand section
> > 12.8/15 correctly, the compiler is always allowed to "unify" the temporary
> > and the argument itself, that is, to construct the temporary directly into
> > the argument, rather than to construct it separately and then copy it.
> >
>
> Yes. I'am curious to know how many modern compilers implement this
> optimization. Someone told that HP's compiler does.
I'm not sure what James is talking about. You seem to be reading it
as the merging of the value parameter with the return value within
a function. I think that he was talking about merging the return
value from one function with the parameter to the next function.
Looking at C d(a + b + c) and using egcs-1.1.
C operator+(C par, C const& rhs)
par1(a);par1+=b;ret1(par1);par2(ret1);par2+=c;ret2(par2);d(ret2);
ret1 and ret2 are removed by merging with par2 and d.
Three copies.
C operator+(C const& lhs, C const& rhs) { loc(lhs)
loc1(a);loc1+=b;ret1(loc1);tmp(ret1);loc2(tmp);loc2+=c;ret2(loc2);d(ret2
);
ret1 and ret2 are removed by merging with tmp and d.
Four copies.
C operator+(C const& lhs, C const& rhs) return loc(lhs) { GNU-NRVO
Same as above
loc1 and loc2 are removed by merging with ret1 and ret2.
Two copies.
Inlining of operator+ and optimization levels had no effect.
Given the last version
tmp(a);tmp+=b;d(tmp);d+=c;
With inlining, it would be visible and tmp could be merged with d.
I'm not sure whether 12.8/15 allows this.
Given the first version
par1(a);par1+=b;par2(par1);par2+=c;d(par2);
With inlining, par1 and par2 could be merged with d.
I doubt that 12.8/15 allows this.
Even if allowed, I doubt that we will see either of these in the
near future if ever.
C d(a); (d += b) += c;
One copy ;-)
John
>If op+ is compiled separately (*really* separately, that is without
>the linker being able to change things or (re-)compile; say with
>dynamic linking), then the compiler doesn't know if the reference
>references a temporary or an actual named object. In the latter
>case, the compiler *must* use a temporary (since otherwise it
>would change its argument), and therefore the only way to be correct
>in any case is to create that temporary.
// original
C y = B() + x;
void B(void* ret);
void operator+(void* ret, const C& lhs, const C& rhs)
{
if (ret!=&lhs) new (ret) C(lhs); // note the if (...)
(*ret)+=rhs;
}
// optimized
C y(don't initialize);
B(&y);
operator+(&y,&y,x);
--
----------------------------------
Siemel B. Naran (sbn...@uiuc.edu)
----------------------------------
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
jka...@otelo.ibmmail.com wrote in message
<6u68lo$igh$1...@nnrp1.dejanews.com>...
>In article <slrn6vqk8d....@localhost.localdomain>,
> sbn...@KILL.uiuc.edu wrote:
...
>|> Play with the attached program.
...
>The code you posted is not very typical, in that there is no
>operator+=. The "classical" idiom involves implementing operator+
>in terms of operator+=, e.g.:
...
>A quick check shows that at least on VC++, there is one less
>temporary using pass by value.
The following doesn't prove anything, except that things are never
as simple as they seem at first glance.
I took James' example and deleted the side effects from the Type
class. Then I compiled with all out optimization both versions
of the operator+ in question. So although James experience with
VC++ would indicate an "extra" constructor is generated for the
"traditional" operator+, in fact VC produces the smallest
(fastest?) code for it...
Modified code:
class Type
{
private:
int num;
public:
Type(int num_) throw() : num(num_) {}
Type(const Type& that) throw() : num(that.num) {}
Type& operator=(const Type&) throw();
~Type() throw() {}
Type& operator+=(Type const& other) throw()
{
num += other.num;
return *this;
}
};
//inline Type operator+(const Type& lhs, const Type& rhs) throw()
//{
// return Type(lhs) += rhs;
//}
inline Type operator+(Type lhs, const Type& rhs) throw()
{
return lhs += rhs;
}
int main()
{
Type a(1), b(10), c(100);
a+b+c;
return 0;
}
"traditional" operator+:
_a$ = -8
_b$ = -4
_c$ = -12
$T260 = -16
$T261 = -20
_main PROC NEAR
; 28 : {
00000 push ebp
00001 mov ebp, esp
00003 sub esp, 20 ; 00000014H
; 29 : Type a(1), b(10), c(100);
; 30 : a+b+c;
00006 lea eax, DWORD PTR _b$[ebp]
00009 lea edx, DWORD PTR _a$[ebp]
0000c push eax
0000d lea ecx, DWORD PTR $T260[ebp]
00010 mov DWORD PTR _a$[ebp], 1
00017 mov DWORD PTR _b$[ebp], 10 ; 0000000aH
0001e mov DWORD PTR _c$[ebp], 100 ; 00000064H
00025 call ??H@YI?AVType@@ABV0@0@Z ; operator+
0002a lea ecx, DWORD PTR _c$[ebp]
0002d mov edx, eax
0002f push ecx
00030 lea ecx, DWORD PTR $T261[ebp]
00033 call ??H@YI?AVType@@ABV0@0@Z ; operator+
; 31 : return 0;
00038 xor eax, eax
; 32 : }
0003a leave
0003b ret 0
_rhs$ = 8
??H@YI?AVType@@ABV0@0@Z PROC NEAR ; operator+, COMDAT
; 17 : inline Type operator+(const Type& lhs, const Type& rhs) throw()
; 18 : {
00000 mov eax, ecx
; 19 : return Type(lhs) += rhs;
00002 mov ecx, DWORD PTR [edx]
00004 mov edx, DWORD PTR _rhs$[esp-4]
00008 add ecx, DWORD PTR [edx]
0000a mov DWORD PTR [eax], ecx
; 20 : }
0000c ret 4
"value based" operator+:
_a$ = -12
_b$ = -8
_c$ = -4
$T261 = -16
$T262 = -16
$T263 = -16
_main PROC NEAR
; 28 : {
00000 push ebp
00001 mov ebp, esp
00003 sub esp, 16 ; 00000010H
00006 push ebx
00007 push esi
00008 push edi
; 29 : Type a(1), b(10), c(100);
; 30 : a+b+c;
00009 push ecx
0000a mov edi, esp
0000c mov DWORD PTR $T261[ebp], esp
0000f push ecx
00010 lea eax, DWORD PTR _a$[ebp]
00013 mov ecx, esp
00015 mov DWORD PTR $T262[ebp], esp
00018 push eax
00019 mov DWORD PTR _a$[ebp], 1
00020 mov DWORD PTR _b$[ebp], 10 ; 0000000aH
00027 mov DWORD PTR _c$[ebp], 100 ; 00000064H
0002e lea esi, DWORD PTR _c$[ebp]
00031 lea ebx, DWORD PTR _b$[ebp]
00034 call ??0Type@@QAE@ABV0@@Z ; Type::Type
00039 mov ecx, edi
0003b mov edx, ebx
0003d call ??H@YI?AVType@@V0@ABV0@@Z ; operator+
00042 lea ecx, DWORD PTR $T263[ebp]
00045 mov edx, esi
00047 call ??H@YI?AVType@@V0@ABV0@@Z ; operator+
; 31 : return 0;
; 32 : }
0004c pop edi
0004d pop esi
0004e xor eax, eax
00050 pop ebx
00051 leave
00052 ret 0
_that$ = 8
??0Type@@QAE@ABV0@@Z PROC NEAR ; Type::Type, COMDAT
; 7 : Type(const Type& that) throw() : num(that.num) {}
00000 mov eax, ecx
00002 mov ecx, DWORD PTR _that$[esp-4]
00006 mov ecx, DWORD PTR [ecx]
00008 mov DWORD PTR [eax], ecx
0000a ret 4
_lhs$ = 8
??H@YI?AVType@@V0@ABV0@@Z PROC NEAR ; operator+, COMDAT
; 22 : inline Type operator+(Type lhs, const Type& rhs) throw()
; 23 : {
00000 mov eax, ecx
; 24 : return lhs += rhs;
00002 mov ecx, DWORD PTR [edx]
00004 add ecx, DWORD PTR _lhs$[esp-4]
00008 mov DWORD PTR [eax], ecx
; 25 : }
0000a ret 4
-----BEGIN PGP SIGNATURE-----
Version: PGP 5.5.5
Comment: Ted A Smith <teds...@acm.org> KeyId: 0x23067e97
iQA/AwUBNgiSWnsm7mQjBn6XEQKuJQCfS9kV5gsLuhXVT8ryfr0fdQcCfJEAoIqR
HnmIYXUjbeEwxnXOXNOehFQ3
=B64/
-----END PGP SIGNATURE-----
On 22 Sep 1998, Francis Glassborow wrote:
> I have been thinking further about this
> We can see that I could have written:
> T result(a);
> result += b;
> result += c;
> which requires only one copy constructor however when I write:
> T result(a+b+c);
> There is the problem of a sequence point after the evaluation of the
> argument passed to the copy ctor (for create result). I suspect that
> means that it must create at least one temporary
T result(operator+(operator+(a,b),c));
There are six sequence points there. Egcs (with the gnu-nrvo language
extension) does construct one temporary, but it is for the result of the
inner operator+ to be passed by reference to the outer one. Result is
copy constructed from this tmp prior to +=(c).
T result(a + b);
also has the sequence point, but uses no temporaries. I have posted
a more detailed analysis elsewhere in this thread. Interested in
your comments there.
> (and that the ctor
> better have a const & parameter and not a plain reference one.
We can be sure of the const& because a+b+c is an rvalue which may
not be bound to a plain reference.
John
On 21 Sep 1998, Siemel Naran wrote:
> On 21 Sep 1998 10:16:47 -0400, John Potter <jpo...@falcon.lhup.edu> wrote:
> // optimization level 2
> void operator+(void* ret, const C& lhs, const C& rhs)
> {
> if (ret!=&lhs) new (ret) C(lhs); // maybe copy
> (*ret)+=rhs;
> } // count: copy=={0,1} , dtor==0
Interesting. Pass. Hopefuly, one of the language lawyers will
comment on this.
> >It may be frightening, you may not like it, but it is the standard.
>
> What are some of the real dangers that could arise?
The fact that side effects of copy-ctor and dtor may not happen. There
was a long thread in csc++ about a year ago. Brian Parker was the
major objector. There were several highly respected people who took
his objections seriously. Considering that 12.8/15 as posted has
little resemblence to CD2, there was obviously a change made at the
November meeting. It seems to me that nothing has changed in the
area of Brian's concern. The rvo is clearly blessed; however, I am
unsure of the other optimizations at the calling end. Is it still
allowed to use the item to be constructed as the return value area
passed to the function? It seems like it is.
What is the difference between slicing the object before calling the
function, and slicing it in the function?
--
James Kanze +33 (0)1 39 23 84 71 mailto: ka...@gabi-soft.fr
+49 (0)69 66 45 33 10 mailto: jka...@otelo.ibmmail.com
GABI Software, 22 rue Jacques-Lemercier, 78000 Versailles, France
Conseils en informatique orientée objet --
-- Beratung in objektorientierter Datenverarbeitung
-----== Posted via Deja News, The Leader in Internet Discussion ==-----
http://www.dejanews.com/rg_mkgrp.xp Create Your Own Free Member Forum
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]