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.

newprint
  • 6,936
  • 13
  • 67
  • 109
  • 1
    If the source is a temporary: `obj = T();` or `obj = foo();` where `foo()` returns a `T`. – juanchopanza Sep 16 '13 at 09:55
  • 1
    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.) – James Kanze Sep 16 '13 at 09:58
  • 4
    @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. – Yakk - Adam Nevraumont Sep 16 '13 at 11:05

1 Answers1

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.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 4
    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). – Simple Sep 16 '13 at 10:03
  • @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. – Dietmar Kühl Sep 16 '13 at 10:06
  • 1
    The copy in the second case can also be replaced with a move, which is district from elision. – Yakk - Adam Nevraumont Sep 16 '13 at 11:08
  • @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. – newprint Sep 17 '13 at 01:48
  • @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 calls – newprint Sep 17 '13 at 01:48
  • @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 ? – newprint Sep 17 '13 at 01:50
  • @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. – Dietmar Kühl Sep 17 '13 at 06:26
  • If the argument is returned, the use of a by-value argument *disables* NRVO within that funftion, [it seems](http://stackoverflow.com/questions/33790822/why-does-copy-elision-make-an-exception-for-formal-parameters). So if he is encouraging an optimization for passing in, be careful what you actually do with that copy. – JDługosz Nov 18 '15 at 22:17