I originally assumed that it is bad practice to move an l-value reference parameter. Is this indeed commonly agreed by the C++ developer community?
When I call a function that has an R-value reference parameter, it's clear that I must expect that the passed object can be moved. For a function that has an L-value reference parameter, this is not so obvious (and before move semantics were introduced with C++11, it wasn't possible at all).
However, some other developers I recently talked to don't agree that moving l-value references shall be avoided. Are there strong arguments against it? Or is my opinion wrong?
Since I was asked to provide a code example, here is one (see below). It's an artificial example just for demonstrating the issue. Obviously, after calling modifyCounter2(), a call of getValue() will cause a segmentation fault. However, if I were a user of getValue() without knowing its internal implementation, I would be very surprised. If, on the other hand, the parameter were an R-value reference, I would be totally clear that I should not use the object anymore after calling modifyCounter2().
class Counter
{
public:
Counter() : value(new int32_t(0))
{
std::cout << "Counter()" << std::endl;
}
Counter(const Counter & other) : value(new int32_t(0))
{
std::cout << "Counter(const A &)" << std::endl;
*value = *other.value;
}
Counter(Counter && other)
{
std::cout << "Counter(Counter &&)" << std::endl;
value = other.value;
other.value = nullptr;
}
~Counter()
{
std::cout << "~Counter()" << std::endl;
if (value)
{
delete value;
}
}
Counter & operator=(Counter const & other)
{
std::cout << "operator=(const Counter &)" << std::endl;
*value = *other.value;
return *this;
}
Counter & operator=(Counter && other)
{
std::cout << "operator=(Counter &&)" << std::endl;
value = other.value;
other.value = nullptr;
return *this;
}
int32_t getValue()
{
return *value;
}
void setValue(int32_t newValue)
{
*value = newValue;
}
void increment()
{
(*value)++;
}
void decrement()
{
(*value)--;
}
private:
int32_t* value; // of course this could be implemented without a pointer, just for demonstration purposes!
};
void modifyCounter1(Counter & counter)
{
counter.increment();
counter.increment();
counter.increment();
counter.decrement();
}
void modifyCounter2(Counter & counter)
{
Counter newCounter = std::move(counter);
}
int main(int argc, char ** argv)
{
auto counter = Counter();
std::cout << "value: " << counter.getValue() << std::endl;
modifyCounter1(counter); // no surprises
std::cout << "value: " << counter.getValue() << std::endl;
modifyCounter2(counter); // surprise, surprise!
std::cout << "value: " << counter.getValue() << std::endl;
return 0;
}
Pass by value, then move is actually a good idiom for objects that you know are movable. As you mentioned, if an rvalue is passed, it'll either elide the copy, or be moved, then within the constructor it will be moved.
Move Constructor And Semantics: std::move() is a function used to convert an lvalue reference into the rvalue reference. Used to move the resources from a source object i.e. for efficient transfer of resources from one object to another.
std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.
Move semantics are used to move resources from one object to another without copying. This is applicable when we try to pass an object to a function or when an object is being returned from a function.
Yes, that is surprising and unconventional.
If you want to permit a move, the convention is to have pass by value. You can std::move
from inside the function as appropriate, while the caller can std::move
into the argument if they decide they want to forfeit ownership.
This convention is where the notion to name std::move
that way came from: to promote explicit forfeiture of ownership, signifying intent. It's why we put up with the name even though it's misleading (std::move
doesn't really move anything).
But your way is theft. ;)
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