6

So I was watching c++ videos on youtube yesterday and came across one the was about C++-11 rvalue reference and move semantics. I think I understand the concept in broad terms, but today when I was going through my code with the TA he asked why i did not us a reference (like std::pair<HostName, IPAddress>& p) in the code below. I had not thought about it at all in this case, but when he asked I remembered the video saying "In C++-11 you should generally use pass by value."

My question is thus: In the code below, would std::pair<HostName, IPAddress> p be better off like std::pair<HostName, IPAddress>& p or not? Will move semantics be used and would it make a difference?

IPAddress NameServer::lookup( const HostName& host ) const {
    auto it = std::find_if( vec.begin(), vec.end(),
                       [host] ( std::pair<HostName, IPAddress> p ) {
        return p.first == host;
    } );
    ...
}
ildjarn
  • 62,044
  • 9
  • 127
  • 211
evading
  • 3,032
  • 6
  • 37
  • 57

1 Answers1

10

In this case, you should pass by const reference. Passing by value makes sense when you eventually want to generate a copy, or a move, of the passed value; if you do not want to copy nor to move, and especially if you want to observe only, you should pass by (const) reference.

Here, your lambda predicate does not need to generate any copy of the pair it receives in input: therefore, there is no reason to pass by value (nor to capture by vaue).

IPAddress NameServer::lookup( const HostName& host ) const {
    auto it = std::find_if( vec.begin(), vec.end(),
        [&host] ( std::pair<HostName, IPAddress> const& p ) {
    //   ^^^^^                                   ^^^^^^
        return p.first == host;
    } );
    ...
}

Consider, instead, this case (typical C++03 code):

struct A
{
    A(string const& s) : _s(s) { }
private:
    string _s;
};

In C++11, since you have move semantics, rather than passing s by constant reference you could simply pass by value and move it into the member variable:

struct A
{
    A(string s) : _s(move(s)) { }
private:
    string _s;
};

This makes sense because we always end up generating a copy of the value being passed.

As correctly pointed out by Benjamin Lindley in the comments, if this is acceptable to you, you could write overloads of the above constructor that take their argument by reference:

struct A
{
    A(string const& s) : _s(s) { }    // 1 copy
    A(string&& s) : _s(move(s)) { }   // 1 move
private:
    string _s;
};

The above version allows performing just one copy for lvalues, and one move for rvalues, whereas the version that passes by value always performs one additional move. Therefore, this solution may be preferable if moving is an expensive operation for your argument types (this is not the case for string, but could be the case for other types).

However, doing so may be cumbersome if your function takes several arguments. To reduce the effort, you could then write a single function template that takes universal references and perfect-forwards its arguments. This Q&A on StackOverflow is relevant to the subject.

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • @juanchopanza: Yes, definitely: overlooked that. Thanks – Andy Prowl Feb 28 '13 at 15:23
  • 1
    *"there is no reason for passing s by constant reference"* -- That's not exactly true. If you want to avoid writing multiple constructors that do close to the same thing, then yes, passing by value then moving is a great option. Especially when there are multiple parameters, because the duplication is exponential. But if you are okay with writing the additional overloads, you *will* benefit from writing one version that takes a const reference, then copies, and another which takes an r-value reference, and then moves. – Benjamin Lindley Feb 28 '13 at 15:29
  • @BenjaminLindley: That is true, but in that case I would rather suggest to write one template version that accepts universal references (and perfect-forwards its arguments), to avoid duplication. No? – Andy Prowl Feb 28 '13 at 15:31
  • @Benjamin: Anyway, I discussed that further, thank you for contributing. – Andy Prowl Feb 28 '13 at 15:41
  • Maybe. I don't have much experience with that, but it seems to me you would run the risk of invoking unwanted constructors there, as well as cryptic error messages. Also your code wouldn't be as self-documenting. – Benjamin Lindley Feb 28 '13 at 15:45
  • @BenjaminLindley: That's true as well. On the other hand, if your constructor takes 3 arguments, you need 8 overloads to cover all the possible combinations. That is IMO a worse issue, but of course it depends on one's situation and preferences. – Andy Prowl Feb 28 '13 at 15:46