16

The accepted answer of this post Pass by value vs pass by rvalue reference says that:

For move-only types (as std::unique_ptr), pass-by-value seems to be the norm...

I'm a little bit doubtful about that. Let's say there is some non-copyable type, Foo, which is also not cheap to move; and some type Bar that has a member Foo.

class Foo {
public:
    Foo(const Foo&) = delete;
    Foo(Foo&&) { /* quite some work */ }
    ...
};

class Bar {
public:
    Bar(Foo f) : f_(std::move(f)) {}    // (1)
    Bar(Foo&& f) : f_(std::move(f)) {}  // (2)
    // Assuming only one of (1) and (2) exists at a time

private:
    Foo f_;
};

Then for the following code:

Foo f;
...
Bar bar(std::move(f));

Constructor (1) incurs 2 move constructions, while constructor (2) only incurs 1. I also remember reading in Scott Meyers's Effective Modern C++ about this but can't remember which item immediately.

So my question is, for move-only types (or more generally, when we want to transfer the ownership of the argument), shouldn't we prefer pass-by-rvalue-reference for better performance?

UPDATE: I'm aware that the pass-by-value constructors/assignment operators (sometimes called unifying ctors/assignment operators) can help eliminate duplicate code. I should say I'm more interested in the case when (1) performance is important, and (2) the type is non-copyable and so there are no duplicate ctors/assignment operators which accept const lvalue reference arguments.

UPDATE 2: So I've found Scott Meyers's blog about the specific problem: http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html. This blog discusses the reason that he advocates in Item 41 of his Effective Modern C++ that:

Consider pass by value only for copyable parameters...that are cheap to move...[and] always copied.

There is an extensive discussion in that item about pass by value vs. rvalue reference, too much to be quoted here. The point is, both ways have their own advantages and disadvantages, but for transferring the ownership of a move-only object, pass by rvalue reference seems to be preferable.

Community
  • 1
  • 1
Zizheng Tai
  • 6,170
  • 28
  • 79
  • 2
    This simply depend on the context, etc. For instance, using pass-by-value avoid having tons of duplicate constructors in some cases. Also, *"[...] Foo, which is also not cheap to move;"*, move constructor should be (very) cheap, if they are not you cannot make general rules on whether you should move or not. – Holt Jul 01 '16 at 08:33
  • @Holt Yes pass-by-value is indeed useful to eliminate duplicate constructors; my primary concern is when performance is more important. And sometimes move ctors just cannot be made (very) cheap. As an example, `std::string` implementations that make use of [small string optimization](http://john-ahlgren.blogspot.com/2012/03/small-string-optimization-and-move.html) cannot avoid a whole copy for short strings even in move ctor. – Zizheng Tai Jul 01 '16 at 08:38
  • 1
    `pass-by-value` is generally a _good-default_ in this case. That does not mean its the most efficient, it's just a good _default_ and shouldn't bother you much in terms of performance. But if you zero-in on this particular function call during performance analysis, then you can probably add a function accepting rvalue ref directly and measure again. – Arunmu Jul 01 '16 at 08:52
  • for move-only types you can definitely use r-value references throughout. There is no benefit to use value parameters, only the potential downside of double move. In general however, for copyable and movable types I would stick to pass by value when that makes sense. – Slava Jul 01 '16 at 08:58
  • Typically non-copyable but movable objects are cheap to move – M.M Jul 03 '16 at 08:36
  • @M.M Typically, yes, but always, no. I think as C++ programmers we should default to the most efficient way of doing things, unless there is a reason not to. (And sometimes there are, e.g. strong exception guarantee. But that's not unmanageable.) – Zizheng Tai Jul 03 '16 at 08:39
  • @ZizhengTai I prefer to default to the easiest to read and least likely to contain bugs; optimization can come later – M.M Jul 03 '16 at 09:00
  • @M.M Well, yes...but does adding two ampersands really harm readability? I would rather argue it adds more inconsistency. Why do we accept to-be-moved-from parameters by value, while our move constructors accept rvalue references? – Zizheng Tai Jul 03 '16 at 09:05

2 Answers2

2

In this case we can have our cake and eat it. A template constructor enabled only for Foo-like references gives us perfect forwarding plus a single implementation of a constructor:

#include <iostream>
#include <utility>

class Foo {
public:
    Foo() {}
    Foo(const Foo&) = delete;
    Foo(Foo&&) { /* quite some work */ }
};

class Bar {
public:
  template<class T, std::enable_if_t<std::is_same<std::decay_t<T>, Foo>::value>* = nullptr>
    Bar(T&& f) : f_(std::forward<T>(f)) {}  // (2)
    // Assuming only one of (1) and (2) exists at a time

private:
    Foo f_;
};

int main()
{
  Foo f;
  Bar bar(std::move(f));

  // this won't compile
//  Foo f2;
//  Bar bar2(f2);

}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • I guess `is_base_of` is better than `is_same` in case subclasses of `Foo` are passed to `Bar`'s ctors :) – Zizheng Tai Jul 01 '16 at 09:28
  • 3
    @ZizhengTai careful. All that would do is allow slicing. It's probably the last thing you want. – Richard Hodges Jul 01 '16 at 09:32
  • But in the "normal" case (where we don't use perfect-forwarding ctor), passing an object of a subclass of `Foo` to `Bar`'s ctor would cause the copy/move ctors to be called. Shouldn't we simulate this behavior in the perfect-forwarding ctor? – Zizheng Tai Jul 01 '16 at 09:35
  • 1
    @ZizhengTai If that's what you really want, sure. But I think in almost all real-world cases, slicing of moved or copied objects is a curse, not a benefit . – Richard Hodges Jul 01 '16 at 09:38
  • Yeah, makes sense. – Zizheng Tai Jul 01 '16 at 09:38
0

Background

It's hard to imagine a class that's expensive to move: move semantics come exactly from the need to give a fast alternative to copies, when semantics allow.

You bring the example of std::string and SSO. However that example is clearly flawed (I doubt they even turned on optimizations) because copying 16 bytes through memcpy should take a bunch of CPU cycles since it can be implemented in 1 SIMD instruction to store them all at once. Also, MSVC 10 is really old.


So my question is, for move-only types (or more generally, when we want to transfer the ownership of the argument), shouldn't we prefer pass-by-rvalue-reference for better performance?

I shan't talk about performance, because it's such a peculiar aspect and can't be analyzed "in general". We'd need concrete cases. Also, compiler optimizations also needs to be considered; quite heavily, actually. And not to forget a thorough performance analysis.

std::unique_ptr is a bit different because (i) can only be moved due to owning semantics (ii) it is cheap to move.

My view. I would say if you have to provide a "faster" alternative as an API, provide both - just like std::vector::push_back. It could have, as you say, slight improvements.
Otherwise, even for move-only types, passing by const-reference still works and if you think it wouldn't, go for pass-by-value.

edmz
  • 8,220
  • 2
  • 26
  • 45
  • 1
    "It's hard to imagine a class that is expensive to move" One way that such a class can arise is if the class passes raw pointers to itself to many other collaborating objects. Such a design may be legitimate if moving it is rare and optimizing moves is much less important than optimizing the lookups. In such cases, move should usually be prohibited, or all of these objects must be tracked down and destroyed or given a new pointer to the new object. This can also easily happen when interacting with C libraries or bindings for some other programming language, where pointers are pretty common. – Chris Beck Jul 01 '16 at 11:09
  • http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html So my humble opinion is that, just as Scott Meyers says, for **move-only types** it seems pass-by-rvalue-reference is the most reasonable choice, at least performance-wise. If the parameter type is both copyable and movable, then yes, pass-by-value is a good default that can be left to be zeroed-in only when needed. But for move-only types, I would argue that pass-by-value doesn't make much sense. Except, of course, that some people think by-value is the way to express sink parameter (while I prefer rvalue ref). – Zizheng Tai Jul 03 '16 at 08:18
  • 1
    And even though `std::unique_ptr` is cheap to move, the standard prefers passing it by rvalue reference. Look at one of the ctors of `std::shared_ptr`: `template shared_ptr(std::unique_ptr&& r );` – Zizheng Tai Jul 03 '16 at 08:45
  • I tried to keep my answer as away as possible from performance because it makes _no_ sense to choose pass-by-value/rvref solely on that. This is more a **style** decision untill concrete examples kick in. Hope that makes sense. – edmz Jul 03 '16 at 11:06
  • 2
    It's actually quite easy to imagine a class that'd be expensive to move (at least as expensive as to copy it). `int[4096]`/`std::array` for example. – Dan M. Nov 26 '18 at 11:52