Let’s say I have the class MyClass
with a correct move constructor and whose copy constructor is deleted. Now I am returning this class like this:
MyClass func()
{
return MyClass();
}
In this case the move constructor gets called when returning the class object and everything works as expected.
Now let’s say MyClass
has an implementation of the <<
operator:
MyClass& operator<<(MyClass& target, const int& source)
{
target.add(source);
return target;
}
When I change the code above:
MyClass func()
{
return MyClass() << 5;
}
I get the compiler error, that the copy constructor cannot be accessed because it is deleted. But why is the copy constructor being used at all in this case?
If any constructor is being called, it means a new object is being created in memory. So, the only difference between a copy constructor and a move constructor is whether the source object that is passed to the constructor will have its member fields copied or moved into the new object.
A copy constructor is called when a new object is created from an existing object, as a copy of the existing object. The assignment operator is called when an already initialized object is assigned a new value from another existing object.
The copy constructor is called because you call by value not by reference. Therefore a new object must be instantiated from your current object since all members of the object should have the same value in the returned instance.
Because passing by value to a function means the function has its own copy of the object. To this end, the copy constructor is called.
Now I am returning this class via lvalue like this:
MyClass func() { return MyClass(); }
No, the returned expression is an xvalue (a kind of rvalue), used to initialise the result for return-by-value (things are a little more complicated since C++17, but this is still the gist of it; besides, you're on C++11).
In this case the move constructor gets called when returning the class object and everything works as expected.
Indeed; an rvalue will initialise an rvalue reference and thus the whole thing can match move constructors.
When I change the code above:
… now the expression is MyClass() << 5
, which has type MyClass&
. This is never an rvalue. It's an lvalue. It's an expression that refers to an existing object.
So, without an explicit std::move
, that'll be used to copy-initialise the result. And, since your copy constructor is deleted, that can't work.
I'm surprised the example compiles at all, since a temporary can't be used to initialise an lvalue reference (your operator's first argument), though some toolchains (MSVS) are known to accept this as an extension.
then would return
std::move(MyClass() << 5);
work?
Yes, I believe so.
However that is very strange to look at, and makes the reader double-check to ensure there are no dangling references. This suggests there's a better way to accomplish this that results in clearer code:
MyClass func()
{
MyClass m;
m << 5;
return m;
}
Now you're still getting a move (because that's a special rule when return
ing local variables) without any strange antics. And, as a bonus, the <<
call is completely standard-compliant.
Your operator return by MyClass&
. So you are returning an lvalue, not an rvalue that can be moved automatically.
You can avoid the copy by relying on the standard guarantees regarding NRVO.
MyClass func()
{
MyClass m;
m << 5;
return m;
}
This will either elide the object entirely, or move it. All on account of it being a function local object.
Another option, seeing as you are trying to call operator<<
on an rvalue, is to supply an overload dealing in rvalue references.
MyClass&& operator<<(MyClass&& target, int i) {
target << i; // Reuse the operator you have, here target is an lvalue
return std::move(target);
}
That will make MyClass() << 5
itself well formed (see the other answer for why it isn't), and return an xvalue from which the return object may be constructed. Though such and overload for operator<<
is not commonly seen.
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