Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accept move-only parameter by value or rvalue reference

Tags:

c++

c++11

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.

like image 908
Zizheng Tai Avatar asked Jul 01 '16 08:07

Zizheng Tai


People also ask

Can you pass an rvalue by reference?

You can pass an object to a function that takes an rvalue reference unless the object is marked as const . The following example shows the function f , which is overloaded to take an lvalue reference and an rvalue reference.

What is the difference between value parameter and reference parameter?

Changes to a value parameter are not visible to the caller (also called "pass by value"). Changes to a reference parameter are visible to the caller ("pass by reference").

What is the difference between L value and R value references?

“l-value” refers to a memory location that identifies an object. “r-value” refers to the data value that is stored at some address in memory. References in C++ are nothing but the alternative to the already existing variable.

What is the purpose of an rvalue reference?

Rvalue references is a small technical extension to the C++ language. Rvalue references allow programmers to avoid logically unnecessary copying and to provide perfect forwarding functions. They are primarily meant to aid in the design of higer performance and more robust libraries.


1 Answers

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

}
like image 166
Richard Hodges Avatar answered Oct 22 '22 23:10

Richard Hodges