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.
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.
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").
“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.
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.
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With