2

I've heard that you should always prefer "pass by value" in C++11 because of the introduction of move semantics. I wanted to see what the hype was all about and constructed a test case. First my class:

struct MyClass {
    MyClass() { }
    MyClass(const MyClass&) { std::cout << "Copy construct" << std::endl; }
    MyClass(MyClass&&) { std::cout << "Move construct" << std::endl; }
    ~MyClass() { }
};

And the test harness:

class Test
{
public:
    void pass_by_lvalue_ref(const MyClass& myClass)
    {
        _MyClass.push_back(myClass);
    }

    void pass_by_rvalue_ref(MyClass&& myClass)
    {
        _MyClass.push_back(std::move(myClass));
    }

    void pass_by_value(MyClass myClass)
    {
        _MyClass.push_back(std::move(myClass));
    }
private:
    std::vector<MyClass> _MyClass;
};

Presumably, pass_by_value should outperform pass_by_lvalue_ref and pass_by_rvalue_ref (together, not separately).

int main()
{
    MyClass myClass;
    Test Test;
    std::cout << "--lvalue_ref--\n";
    Test.pass_by_lvalue_ref(myClass);
    std::cout << "--rvalue_ref--\n";
    Test.pass_by_rvalue_ref(MyClass{});
    std::cout << "--value - lvalue--\n";
    Test.pass_by_value(myClass);
    std::cout << "--value - rvalue--\n";
    Test.pass_by_value(MyClass{});
}

This is my output on GCC 4.9.2 with -O2:

--lvalue_ref--
Copy construct
--rvalue_ref--
Move construct
Copy construct
--value - lvalue--
Copy construct
Move construct
Copy construct
Copy construct
--value - rvalue--
Move construct

As you can see, the non-pass_by_value functions requires a total of 2 copy constructs and 1 move construct. The pass_by_value function requires a total of 3 copy constructs and 2 move constructs. It looks like that, as expected, the object is going to be copied anyway, so why does everyone say pass by value?

  • The test harness is broken. Some of your `push_back`s trigger reallocations and others don't. Passing by value isn't a performance gain over a pair of overloads; it's a maintainability gain. – T.C. Feb 26 '15 at 02:33
  • As T.C. says, make sure your `vector` is large enough to prevent reallocations - http://coliru.stacked-crooked.com/a/e424a3073bdc727c Also, here's your [another version of your example](http://coliru.stacked-crooked.com/a/94501c358569ba8b) that causes reallocations, but avoids the copy constructions when that happens, notice the difference? (I made `MyClass`'s move constructor `noexcept`) – Praetorian Feb 26 '15 at 02:34
  • Mostly duplicate of http://stackoverflow.com/q/21605579/103167 – Ben Voigt Feb 26 '15 at 02:46
  • 1
    `_Typename` as the name of a variable is a really horrible naming convention... – M.M Feb 26 '15 at 02:55
  • 2
    @MattMcNabb Not to mention it uses a reserved identifier... – T.C. Feb 26 '15 at 03:23

2 Answers2

2

First, your reporting is entirely flawed. Each of your functions pushes back to the same vector. When that vector runs out of capacity (which depends upon how many items you've inserted so far), it is going to trigger a re-allocation which will require more moves and/or copies than an insertion which doesn't trigger an allocation.

Second, std::vector::push_back has a strong exception safety guarantee. So if your move constructor is not noexcept, it will not use it (unless the class is non-copyable). It will use the copy constructor instead.

Third,

I've heard that you should always prefer "pass by value" in C++11 because of the introduction of move semantics.

I'm pretty sure you didn't hear that from any reputable source. Or are actually inappropriately paraphrasing what was actually said. But I don't have the source of the quote. What is usually advised is actually that if you are going to copy your arguments in your function anyway, don't. Just do it in the parameter list (via pass by value). This will allow your function to move r-value arguments straight to their destination. When you pass l-values, they will be copied, but you were going to do that anyway.

Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • It was Dave Abrahams, here is (was) [the original](http://web.archive.org/web/20140113221447/http:/cpp-next.com/archive/2009/08/want-speed-pass-by-value/) but Scott Meyer also said it. You can find several responses from equally famous experts by searching for the title of his blog post, ["Want Speed? Pass by Value."](https://www.google.com/search?q=want+speed%2C+pass+by+value) – Ben Voigt Feb 26 '15 at 02:45
  • 1
    @BenVoigt: If the David Abrahams article is the one OP is referring to, then it is as I said *"inappropriately paraphrasing what was actually said"*. – Benjamin Lindley Feb 26 '15 at 02:48
  • You're right, Dave didn't say "prefer", he worded it as an absolute "Guideline: Don’t copy your function arguments. Instead, pass them by value and let the compiler do the copying." Although, in context he clearly didn't mean it as an absolute. I think "prefer" is a fair way of paraphrasing it. – Ben Voigt Feb 26 '15 at 02:49
  • 1
    @BenVoigt: It's not the "prefer" that's the problem. It's the "always". The OP didn't mention anything about the important condition that you are copying the arguments anyway. – Benjamin Lindley Feb 26 '15 at 02:51
  • You might make that clear in your answer. As well as the fact that the more recent wisdom is not to do it even then. – Ben Voigt Feb 26 '15 at 02:53
0

If you are going to make an internal copy, then passing by value will do exactly one move construct more than the pair of overloads (pass by rvalue ref)+(pass by const lvalue ref).

If move construct is cheap, this is a small amount of runtime overhead in exchange for less compile time and code maintenance overhead.

The idiom is "Want speed? Making a copy anyhow? Pass by value, instead of by const lvalue reference." in reality.

Finally, your benchmark is flawed as you failed to reserve(enough) before your push backs. Reallocation can cause extra operations. Oh, and make your move constructor noexcept, as conforming libraries will prefer a copy to a move if move can throw in many situations.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524