56

Correct me if I'm wrong. Say I have:

struct X
{
    std::string mem_name;

    X(std::string name)
        : mem_name(std::move(name)) 
    {}
    ...
};
struct Y
{
    std::string mem_name;

    Y(const std::string &name)
        : mem_name(name) 
    {}
    ...
};

In X's ctor, name is obviously a copy of whatever argument got passed to X, X invokes the move ctor of std::string to initialize mem_name, right?

Let's call that a copy-then-move on X*; two operations: COPY, MOVE.

In Y's ctor, name is a const ref, which means there's no actual copy of the element because we're dealing directly with the argument passed from wherever Y's object needs to be created. But then we copied name to initialise mem_name in Y; one operation: COPY. Surely it should therefore be a lot faster (and preferable to me)?

In Scott Meyer's GN13 talk (around time-frame 8:10 and 8:56), he talks about "Want speed? Pass by value" and I was wondering is there any performance difference or loss in passing arguments (or strings to be precise) by reference and passing by value "in order to gain speed?"

I'm aware of the fact that passing arguments by value can be expensive, especially when dealing with large data.

Maybe (clearly?) there's something I'm missing from his talk?

Danh
  • 5,916
  • 7
  • 30
  • 45
iamOgunyinka
  • 1,040
  • 1
  • 9
  • 17
  • Sounds like a classic trade-off between memory and CPU, but I don't see where the speed up happens. Have to copy, which takes time. But you don't have thread contention issues with separate copies. Was he speaking in the context of asynch tasks>? – duffymo Feb 06 '14 at 14:30
  • You might want to read the [original article](http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/) as well. BTW, I did some basic formatting improvement to your post, but you might want to spend more time on formatting & indentation (verifying it in the preview). – Angew is no longer proud of SO Feb 06 '14 at 14:31
  • 1
    @duffymo The idea is that sometimes, the copy can be elided by direct in-place construction when taking by value, but that's not possible when taking by const ref. – Angew is no longer proud of SO Feb 06 '14 at 14:32
  • No, it was when he was explaining how std::move works, when discussing moving from a const object – iamOgunyinka Feb 06 '14 at 14:32
  • 5
    Related questions: [Is the pass-by-value-and-then-move construct a bad idiom?](http://stackoverflow.com/q/21035417/341970) and [When is overloading pass by reference (l-value and r-value) preferred to pass-by-value?](http://stackoverflow.com/q/18303287/341970) Long story short: **Want speed? Measure.** (by Howard Hinnant) – Ali Feb 06 '14 at 15:17
  • 1
    For a nice explanation of the optimization opportunities enabled by pass-by-value, check out Chandler Carruth's excellent [BoostCon 2013 keynote (youtube video)](http://www.youtube.com/watch?v=eR34r7HOU14). – ComicSansMS Feb 06 '14 at 15:46

3 Answers3

67

The idea of "Want speed? Pass by value"(1) is that sometimes, the copy can be elided. Taking your classes X and Y, consider this usecase:

// Simulating a complex operation returning a temporary:
std::string foo() { return "a" + std::string("b"); }


struct X
{
  std::string mem_name;
  X(std::string name): mem_name(std::move(name)) {}
};

struct Y
{
  std::string mem_name;
  Y(const std::string &name): mem_name(name) {}
};


int main()
{
  X(foo());
  Y(foo());
}

Now let's analyse both the construction cases.

X first. foo() returns a temporary, which is used to initialise the object name. That object is then moved into mem_name. Notice that the compiler can apply Return Value Optimisation and construct the return value of foo() (actually even the return value of operator+) directly in the space of name. So no copying actually happens, only a move.

Now let's analyse Y. foo() returns a temporary again, which is bound to the reference name. Now there's no "externally supplied" space for the return value, so it has to be constructed in its own space and bound to the reference. It is then copied into mem_name. So we are doing a copy, no way around it.

In short, the outcome is:

  • If an lvalue is being passed in, both X and Y will perform a copy (X when initialising name, Y when initialising mem_name). In addition, X will perform a move (when initialising mem_name).

  • If an rvalue is being passed in, X will potentially only perform a move, while Y has to perform a copy.

Generally, a move is expected to be an operation whose time requirements are comparable to those of passing a pointer (which is what passing by reference does). So in effect, X is no worse than Y for lvalues, and better for rvalues.

Of course, it's not an absolute rule, and must be taken with a grain of salt. When in doubt, profile.


(1) The link is prone to being temporarily unavailable, and as of 11-12-2014, it seems broken (404). A copy of the contents (albeit with weird formatting) seems available at several blog sites:

Alternatively, the original content might be accessible through the wayback machine.

Also note that the topic has in general stirred up quite a discussion. Googling the paper title brings up a lot of follow-ups and counter-points. To list an example of one of these, there's "Want speed? Don't (always) pass by value" by SO member juanchopanza

Community
  • 1
  • 1
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 2
    it would be great to find a new link, its 404 now. – v.oddou Dec 11 '14 at 02:26
  • 1
    @v.oddou :-( I can't find one now; if you or anyone else stumbles upon one, feel free to edit it in. – Angew is no longer proud of SO Dec 11 '14 at 08:11
  • 1
    I found that : http://m.blog.csdn.net/blog/CPP_CHEN/17528669 and some follow up by a different author for mitigation: http://juanchopanzacpp.wordpress.com/2014/05/11/want-speed-dont-always-pass-by-value/ – v.oddou Dec 11 '14 at 08:55
  • 1
    The article(1) is avaliable here: https://web.archive.org/web/20140205194657/http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ – Correa Jun 03 '15 at 19:37
  • and the article just got removed from the wayback machine due to a robots.txt ... – Giel Jun 15 '16 at 10:02
  • What I think to be another copy can be found here (think because it's been a while since I read the original): https://joseluisestebanaparicio.blogspot.nl/2010/06/want-speed-pass-by-value.html – Giel Jun 15 '16 at 12:12
  • I have a question. Firstly I'm talking about C++11. According to the Rule of Five, when declaring a copy constructor, we should also declare a move constructor. In such case `struct Y` is constructed from lvalue with a copy operation and from rvalue with only a move operation, which results in at least same performance in comparison with `struct X` (or even better in lvalue case for eliding a move operation). Then is passing by value still recommended? – jerry_fuyi Jul 24 '19 at 11:48
  • 4
    @jerry_fuyi The code in the answer does not deal with passing `X` or `Y` somewhere, it deals with passing `std::string` somewhere. Of course, [overloading for `const std::string&` and `std::string&&`](http://coliru.stacked-crooked.com/a/4c4b7fdd0bb7a53e) would be the safest and most efficient thing to do, but it's extra code and if your function has more such parameters, it explodes combinatorically. The idea is that the value-taking function works almost as well as the lvalue+rvalue overload pair. – Angew is no longer proud of SO Jul 24 '19 at 12:12
  • 1
    @Angew I understand. The constructor taking `std::string` is not what Rule of Three/Five involves. Thanks. – jerry_fuyi Jul 24 '19 at 12:14
  • The article is still available here: https://archive.is/ROtoH – Michał Góral Feb 18 '20 at 17:03
16

There are no general rules for optmization. Pass-by-value can give some big wins in C++11 with move semantics, alongside copy elision.

If you really want speed, Profile your code.

cdmh
  • 3,294
  • 2
  • 26
  • 41
4

If you really don't mind exposing non-references in your API (which SHOULD BE a sign that internally you will be copying/assigning given object) then using copies is ok.

Copy elision is faster than moving, and if it can't be elided (for various reasons, like too long call chain of dependent function calls), then C++ guarantees move semantics.

Red XIII
  • 5,771
  • 4
  • 28
  • 29