简体   繁体   中英

Accept move-only parameter by value or rvalue reference

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.

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);

}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM