Pass by Value VS Pass by Rvalue Reference

Pass by value vs pass by rvalue reference

The rvalue reference parameter forces you to be explicit about copies.

Yes, pass-by-rvalue-reference got a point.

The rvalue reference parameter means that you may move the argument, but does not mandate it.

Yes, pass-by-value got a point.

But that also gives to pass-by-rvalue the opportunity to handle exception guarantee: if foo throws, widget value is not necessary consumed.

For move-only types (as std::unique_ptr), pass-by-value seems to be the norm (mostly for your second point, and first point is not applicable anyway).

EDIT: standard library contradicts my previous sentence, one of shared_ptr's constructor takes std::unique_ptr<T, D>&&.

For types which have both copy/move (as std::shared_ptr), we have the choice of the coherency with previous types or force to be explicit on copy.

Unless you want to guarantee there is no unwanted copy, I would use pass-by-value for coherency.

Unless you want guaranteed and/or immediate sink, I would use pass-by-rvalue.

For existing code base, I would keep consistency.

Pass by value or rvalue-ref

The pass-by-value function is sufficient (and equivalent), as long as the argument type has an efficient move constructor, which is true in this case for std::vector.

Otherwise, using the pass-by-value function may introduce an extra copy-construction compared to using the pass-by-rvalue-ref function.

See the answer https://stackoverflow.com/a/7587151/1190077 to the related question Do I need to overload methods accepting const lvalue reference for rvalue references explicitly? .

Advantages of pass-by-value and std::move over pass-by-reference

  1. Did I understand correctly what is happening here?

Yes.


  1. Is there any upside of using std::move over passing by reference and just calling m_name{name}?

An easy to grasp function signature without any additional overloads. The signature immediately reveals that the argument will be copied - this saves callers from wondering whether a const std::string& reference might be stored as a data member, possibly becoming a dangling reference later on. And there is no need to overload on std::string&& name and const std::string& arguments to avoid unnecessary copies when rvalues are passed to the function. Passing an lvalue

std::string nameString("Alex");
Creature c(nameString);

to the function that takes its argument by value causes one copy and one move construction. Passing an rvalue to the same function

std::string nameString("Alex");
Creature c(std::move(nameString));

causes two move constructions. In contrast, when the function parameter is const std::string&, there will always be a copy, even when passing an rvalue argument. This is clearly an advantage as long as the argument type is cheap to move-construct (this is the case for std::string).

But there is a downside to consider: the reasoning doesn't work for functions that assign the function argument to another variable (instead of initializing it):

void setName(std::string name)
{
m_name = std::move(name);
}

will cause a deallocation of the resource that m_name refers to before it's reassigned. I recommend reading Item 41 in Effective Modern C++ and also this question.

Pass-by-value and std::move vs forwarding reference

But it seems to me that passing by either const reference or rvalue reference can save a copy in some situations.

Indeed, but it requires more overloads (and even worst with several parameters).

Pass by value and move idiom has (at worst) one extra move. which is a good trade-off most of the time.

maybe using a forwarding reference to avoid writing both constructors.

Forwarding reference has its own pitfalls:

  • disallows {..} syntax for parameter as {..} has no type.
    Test2 a({5u, '*'}); // "*****"
    would not be possible.
  • is not restrict to valid types (requires extra requires or SFINAE).
    Test2 b(4.2f); // Invalid, but `std::is_constructible_v<Test2, float>` is (falsely) true.
    would produces error inside the constructor, and not at call site (so error message less clear, and SFINAE not possible).
  • for constructor, it can take precedence over copy constructor (for non-const l-value)
    Test2 c(a); // Call Test2(T&&) with T=Test2&
    // instead of copy constructor Test2(const Test2&)
    would produce error, as std::string cannot be constructed from Test2&.

Pass input parameters as rvalue references?

Is it a good idea to pass parameters as rvalue references just to make it clear that you're going to manipulate the argument?

Not in general. In particular, it depends on how you are manipulating the argument.

If you accept a parameter by rvalue, then you are implying that the argument will be left in an unspecified, typically moved-from state. If that is what the function does, then rvalue reference is indeed appropriate. Note that depending on use case, passing the argument by value is often a good alternative in this case.

That is the opposite implication that you would want to make when you modify the object such that the caller might find the modified state useful. Lvalue reference to non-const implies that the argument will be manipulated in this way.

Pass by value/reference/rvalue with a std::move(str) arg

When i is pass-by-value, d becomes empty,

To be accurate, d will be in some valid state not specified in the C++ standard. Empty is one possibility.

std::move itself never causes the move constructor to be called directly. Neither does binding an rvalue reference to an object cause move constructor to be called directly.

Only initialising an object with a non-const rvalue will cause the argument to be moved from. In the example, std::string i is initialised with a non-const rvalue and the move constructor will be called.

As an aside why does the code in the current state compile if d is cast to an x-value?

Because the type has a (non-deleted) move constructor. Therefore the argument can be initialised from an rvalues.

I had thought if we had std::string i, a copy of the rvalue reference is made.

std::string i is not a reference. It is a variable of type std::string and as such there is an object of type std::string associated with the variable. That object is initialised with the expression that is passed into the function as argument.

Also, if I observe that the output of d is still the same as prior to applying std::move, what does this mean in this case?

If you call the uncommented version of the function with an rvalue, then the argument will be moved from. If the value is same as it was, then it simply means that the value is the same. You cannot assume that the value will be the same nor that it won't be the same.

Does it mean that d is still occupying the space it originally occupied?

Assuming that by "space" you mean the storage where the variable is, then of course it is still occupying the same storage. The address of an object never changes through the lifetime of the object.

C++ pass parameter by rvalue reference if possible, otherwise copy the lvalue reference

The reference says that it only accepts rvalue reference parameters.

No, this is forwarding reference, which could serve as both lvalue reference and rvalue reference, according to the value category of the passed-in argument.

How can I make foo work like this?

The point of declaring a forwarding reference is (1) type deduction is necessary, that means you need to make foo a function template here; (2) the parameter x has the exact form of T&& for the template parameter T. e.g.

template <typename T>
void foo(T&& x){
x = 2;
}

then

int x = 1;
foo(x); // lvalue passed, T is deduced as int&, parameter's type is int&
foo(1); // rvalue passed, T is deduced as int, parameter's type is int&&

Note this is true for std::make_tuple too, even it's using template parameter pack. And better to bear in mind that even forwarding reference looks like rvalue reference but they're different things.

BTW: std::forward is usually used with forwarding reference to preserve the value category of the argument e.g. when forwarding it to other functions.

Overloading for pass-by-value and pass-by-rvalue-reference

The intention is clearly that the first overload moves the contents of newParameter from wherever up the chain of subroutine-calls the newParameter object originated, whilst the second overload creates a brand new copy

Which is not really how you do it. You have two sane options:

Approach A

You write just the value overload and then move from it anyway - that means you'll always pay a constructor price, either move or copy.

Approach B

You write overloads for (const T&) and (T&&). That way you copy in the first one and skip the move CTOR in the second one using perfect forwarding.


I recommend approach A as a default, and B only when the c-tor call actually matters that much.

Passing function rvalue reference, when it takes as parameter by value

If SomeObject has a move constructor defined, then calling func(std::move(obj)) will construct the obj parameter using the move constructor. If there is no move constructor defined, then the obj parameter will be constructed using the copy constructor instead.

If there is no copy or move constructor defined, the compile will fail with an error.

If you change the obj parameter to be an rvalue reference, no construction will be done at all, the result of std::move(obj) will be passed as-is, like any other reference parameter.

c++ How to pass an rvalue Reference to another function

You have to use std::move to convert element to rvalue. As a named variable element itself is an lvalue, even its type is an rvalue reference.

queue.push(std::move(element));

Note that types and value categories are two independent things.

(emphasis mine)

Each C++ expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category.

...

The following expressions are lvalue expressions:

  • the name of a variable, a function, a template parameter object (since C++20), or a data member, regardless of type, such as std::cin
    or std::endl. Even if the variable's type is rvalue reference, the
    expression consisting of its name is an lvalue expression
    ;


Related Topics



Leave a reply



Submit