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?
It should be a non-const
rvalue reference.
If an object is placed in read-only memory, you can't steal resources from it, even if its formal lifetime is ending shortly. Objects created as const
in C++ are allowed to live in read-only memory (using const_cast
to try to change them results in undefined behavior).
A move constructor should normally take a non-const reference.
If it were possible to move from a const object it would usually imply that it was as efficient to copy an object as it was to "move" from it. At this point there is normally no benefit to having a move constructor.
You are also correct that if you have a variable that you are potentially going to want to move from then it will need to be non-const.
As I understand it this is the reason that Scott Meyers has changed his advice on returning objects of class type by value from functions for C++11. Returning objects by const qualified value does prevent unintentionally modification of a temporary object but it also inhibits moving from the return value.
Should a move constructor take a const or non-const rvalue reference?
It should take non-const rvalue reference. The rvalue references first of all don't make sense in their const forms simply because you want to modify them (in a way, you want to "move" them, you want their internals for yourself ).
Also, they have been designed to be used without const and I believe the only use for a const rvalue reference is something very very arcane that Scott Meyers mentioned in this talk.
Am I right in this line of reasoning? That I should stop returning things that are const?
This is a bit of too general question to answer I reckon. In this context, I think it's worth mentioning that there's std::forward
functionality that will preserve both rvalue-ness and lvalue-ness as well as const-ness and it will also avoid creating a temporary as a normal function would do should you return anything passed to it.
This returning would also cause the rvalue reference to be "mangled" into lvalue reference and you generally don't want that, hence, perfect forwarding with the aforementioned functionality solves the issue.
That being said, I suggest you simply take a look at the talk that I posted a link to.
In addition to what is said in other answers, sometimes there are reasons for a move constructor or a function to accept a const T&&
. For example, if you pass the result of a function that returns a const
object by value to a constructor, T(const T&)
will be called instead of T(T&&)
as one would probably expect (see function g
below).
This is the reason behind deleting overloads that accept const
T&&
for std::ref
and std::cref
instead of those that accept T&&
.
Specifically, the order of preference during overload resolution is as follows:
struct s {};
void f ( s&); // #1
void f (const s&); // #2
void f ( s&&); // #3
void f (const s&&); // #4
const s g ();
s x;
const s cx;
f (s ()); // rvalue #3, #4, #2
f (g ()); // const rvalue #4, #2
f (x); // lvalue #1, #2
f (cx); // const lvalue #2
See this article for more details.