Passing by Value and copy elision optimization
Asked Answered
S

1

9

I came upon https://web.archive.org/web/20120707045924/cpp-next.com/archive/2009/08/want-speed-pass-by-value/

Author's Advice:

Don’t copy your function arguments. Instead, pass them by value and let the compiler do the copying.

However, I don't quite get what benefits are gained in the two example presented in the article:

// Don't
T& T::operator=(T const& x) // x is a reference to the source
{ 
    T tmp(x);          // copy construction of tmp does the hard work
    swap(*this, tmp);  // trade our resources for tmp's
    return *this;      // our (old) resources get destroyed with tmp 
}

vs

// DO
T& operator=(T x)    // x is a copy of the source; hard work already done
{
    swap(*this, x);  // trade our resources for x's
    return *this;    // our (old) resources get destroyed with x
}

In both cases one extra variable is created, so where are the benefits? The only benefit I see, is if the temp object is passed into second example.

Smallpox answered 16/9, 2013 at 9:50 Comment(3)
If the source is a temporary: obj = T(); or obj = foo(); where foo() returns a T.Mopey
That's actually not very good advice. It exposes what should be an implementation detail (whether you copy the argument or not) in the interface, which is extremely poor software engineering. There may be times when the profiler says you have to, but otherwise, you stick to the coding guidelines. (The ubiquitous guideline seems to be pass class types by reference, everything else by value, although this too could be seen as premature optimization.)Tomokotomorrow
@jameskanze "this function copies the argument's state" is a reasonable interface feature. Among other things, it has impact on the cost, it tells you what features of the arguments type must be implemented, and it even informs the user about the functionality. Part of the genius of C++11 was that copying and moving are important and district operations on data, and exposing it happening is important.Drama
S
8

The point is that depending on how the operator is called, a copy may be elided. Assume you use your operator like this:

extern T f();
...
T value;
value = f();

If the argument is taken by by T const& the compiler has no choice but to hold on to the temporary and pass a a reference on to your assignment operator. On the other hand, when you pass the argument by value, i.e., it uses T, the value returned from f() can be located where this argument is, thereby eliding one copy. If the argument to the assignment is an lvalue in some form, it always needs to copy, of course.

Sanitize answered 16/9, 2013 at 9:54 Comment(8)
There is also a difference regarding exceptions. In the case of T const& the copy constructor can throw an exception inside the called function, but in the case of T then the exception will be thrown in the context of the caller instead (this means the assignment operator can be marked noexcept even if the copy constructor can throw).London
@Simple: This is an interesting point, actually! Although in the end it doesn't make much of a difference: the entire expression can still throw. It may make it easier for templates which don't have to faff about with a conditional noexcept as a result.Dissociable
The copy in the second case can also be replaced with a move, which is district from elision.Drama
@DietmarKühl Thank you for your reply, unfortunately your answer doesn't make any sense to me. So, let me paraphrase your reply and try to see if it makes sense (to you and other readers). Here is what I got so far: We have two operator(s)= first one being regular class assignment operator T::operator=(...), and second one is regular (function) operator=(...). Following your code, there are two possibilities for final assignment.Smallpox
@DietmarKühl First possibility, value::operator=(f()), where rvalue temp argument binds to const lvalue reference parameter(you can do it, since it is const), and because parameter x is lvalue inside (*as it was discussed in the article), it will be copied to tmp no matter what, i.e we can't elide copy. In perspective: {temp_anon} created - one copy, tmp - second copy -- Memory for 2 objects is occupied + 2 ctor calls + 2 dtor callsSmallpox
@DietmarKühl Second possibility, operator=(T x) is called. i.e operator=(f()); In this case x is already created for us by the compiler, and we don't need to copy it as in prev. In perspective: {temp_anon} created - one copy -- Memory for single object is occupied + 1 ctor calls + 1 dtor calls. I am right ?Smallpox
@newprint: Yes, the point is that the copies may be elided entirely when passing by value while at least on extra copy is needed when passing by reference. As was pointed out in the comments, there are two additional benefits: even if the copy can't be elided, the value parameter can be moved from and any exception would be thrown before the assignment operator is even called.Dissociable
If the argument is returned, the use of a by-value argument disables NRVO within that funftion, it seems. So if he is encouraging an optimization for passing in, be careful what you actually do with that copy.Innerve

© 2022 - 2024 — McMap. All rights reserved.