In several places I've seen the recommended signatures of copy and move constructors given as:
struct T
{
T();
T(const T& other);
T(T&& other);
};
Where the copy constructor takes a const reference, and the move constructor takes a non-const rvalue reference.
As far as I can see though, this prevents me taking advantage of move semantics when returning const objects from a function, such as in the case below:
T generate_t()
{
const T t;
return t;
}
Testing this with VC11 Beta, T
's copy constructor is called, and not the move constructor. Even using return std::move(t);
the copy constructor is still called.
I can see how this makes sense, since t
is const so shouldn't bind to T&&
. Using const T&&
in the move constructor signature works fine, and makes sense, but then you have the problem that because other
is const, you can't null its members out if they need to be nulled out - it'll only work when all members are scalars or have move constructors with the right signature.
It looks like the only way to make sure the move constructor is called in the general case to have made t
non-const in the first place, but I don't like doing that - consting things is good form and I wouldn't expect the client of T
to know that they had to go against that form in order to increase performance.
So, I guess my question is twofold; first, should a move constructor take a const or non-const rvalue reference? And second: am I right in this line of reasoning? That I should stop returning things that are const?
return T()
would do, anyway. Though I can still see certain use cases, I think they are rare. And of course, an rvalue reference from which you cannot steal resources is not really of any value compared to an lvalue reference. Interresting question, though. – Christian Rauconst
objectt
is constructed into the same location as the non-const return value. Then if necessary that non-const return value can be moved by the caller via the non-const move constructor (or a move assignment operator). In the case of construction, though, it would again be eligible for copy elision andt
could be constructed directly into whatever's being initialized using a call togenerate_t()
. What's obstructing VC11 from making this optimization? – Steve JessopT
doesn't need to go against that form in order to increase performance. However as a QoI issue, VC11 (with the options you gave it) has dropped the ball. Still, in general I don't think it's quite correct to say that the client of a class shouldn't need to think about which objects can be moved from in order to increase performance. It's just that in this case I don't think they do. – Steve Jessopconst T&&
which would be bad) then the move constructor would be viable, but would be elided. So the only difference should be whether a move or copy is elided, but it should be elided either way. – Jonathan Wakely