1

Consider this code:

void doSomethingWithString(string& mString)
{
     // mString gets modified in here
}

string getCopy1(const string& mString) 
{ 
    string result{mString}; doSomethingWithString(result); return result; 
}

string getCopy2(string mString) 
{ 
    doSomethingWithString(mString); return mString; 
}

Between getCopy1 and getCopy2, what one:

  • Clearly shows that the passed string isn't going to get modified
  • Clearly shows that the user will get a new string returned
  • Is faster / can be better optimized by the compiler (C++11 enabled, consider move semantics)

?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    Not sure what the question is. Don’t you forego the answer? – Konrad Rudolph Jun 10 '13 at 13:03
  • @KonradRudolph The questions are the three listed points. Basically, both getCopy1 and getCopy2 do the same thing - what one is faster and more expressive? – Vittorio Romeo Jun 10 '13 at 13:05
  • Shameless plug: see http://stackoverflow.com/questions/16944715/constructor-parameter-style/ -- I hesitate to mark the question as duplicate because the other one is specifically about constructors (and initializing member variables from a parameter) and there are other minor differences but the big picture is the same. – syam Jun 10 '13 at 13:05
  • related: http://stackoverflow.com/q/10231349/951890 – Vaughn Cato Jun 10 '13 at 13:10
  • @syam: As far as I understand, const& should be used unless I'm planning to never use again the original string argument (then I should move the mString argument). So, in my case, getCopy1 is better. Am I correct? – Vittorio Romeo Jun 10 '13 at 13:30
  • 4
    If you're making a copy *in any case*, do it in the parameter, so `getCopy2` it is. – Xeo Jun 10 '13 at 13:31
  • 2
    @Vee: No, the pass-by-value is better in your case. See the answers, they explain it. – syam Jun 10 '13 at 13:40

4 Answers4

4

Either version clearly shows there is no intent to modify the value that is passed in.

getCopy2 is more efficient in the case where an rvalue is being passed as the parameter. In that case, no copy needs to be done, since the parameter will be moved instead of copied, and you are doing no internal copying. For getCopy1, you always force at least one copy to be made. If an lvalue is being passed as the parameter, then a move needs to be done instead of creating a reference. Which is more efficient depends on a lot of details of the compiler and the string implementation, but the speed of the move should be comparable to the speed of creating a reference.

I don't see any difference as far as the return value.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • I would suspect that a good optimizer might fix this, but I agree. getCopy2 is better, both stylistically, and more optimizers would handle that case better. – MobA11y Jun 10 '13 at 13:35
  • Thanks, I understand now. I improved my library using "getCopy2-style" argument passing where possible. (https://github.com/SuperV1234/SSVUtils/commit/6f142392f9409c99d846a6ba3e7dbc2c861dccd2) – Vittorio Romeo Jun 10 '13 at 14:38
3

Between getCopy1 and getCopy2, what one:

  • Clearly shows that the passed string isn't going to get modified

Both: first, because although it takes a reference, the reference is const; second, because it creates it's own copy.

  • Clearly shows that the user will get a new string returned

Both: they both return a string instance;

  • Is faster / can be better optimized by the compiler (C++11 enabled, consider move semantics)

The second is better to use: if you are making a copy of the input parameter, better do it in the parameter itself (better let the compiler make the copy, on call); It is also better because in the case of a rvalue reference, there is no extra copy done.

Community
  • 1
  • 1
utnapistim
  • 26,809
  • 3
  • 46
  • 82
3

Ran some tests, with G++ 4.8.1 on Linux Mint x64. Flags: -std=c++11 -O3 -DNDEBUG

void doSomethingWithString(string& mString) { mString[0] = 'f'; }

string getCopy1(const string& mString)
{
    string result{mString}; doSomethingWithString(result); return result;
}

string getCopy2(string mString)
{
    doSomethingWithString(mString); return mString;
}

int main()
{
    string s{"132958fdgefi9obm3890g54"};
    string t{""};

    {
        startBenchmark();
        for(int i{0}; i < 20000000; ++i) t = getCopy1(s);
        log(endBenchmark(), "getCopy1 variable");
    }
    {
        startBenchmark();
        for(int i{0}; i < 20000000; ++i) t = getCopy1("abcsd");
        log(endBenchmark(), "getCopy1 literal");
    }

    {
        startBenchmark();
        for(int i{0}; i < 20000000; ++i) t = getCopy2(s);
        log(endBenchmark(), "getCopy2 variable");
    }
    {
        startBenchmark();
        for(int i{0}; i < 20000000; ++i) t = getCopy2("abcsd");
        log(endBenchmark(), "getCopy2 literal");
    }

    return 0;
}

Output:

[getCopy1 variable] 1236 ms
[getCopy1 literal] 1845 ms
[getCopy2 variable] 993 ms
[getCopy2 literal] 857 ms

Conclusion:

getCopy2 is faster, especially with rvalues (literal strings).

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
2

getCopy2 can be often better optimized. This is neatly explained in "Want Speed? Pass by Value."

Andrzej
  • 5,027
  • 27
  • 36