Confusion regarding rvalue references

Pages: 12
So, I've just recently discovered rvalue references in C++11, and I don't think I quite get it. Let's say I was making a wrapper class around std::string with the following code.

1
2
3
4
5
6
7
8
9
10
11
class String
{
private:
	std::string str;

public:
	String() {}
	String(const char* s);
	String(String&& s);
	String(const String& s);
};


Now, say I create a string using the following code:
1
2
3
String str = String("Hello!");
// yes, this is redundant since I could just pass a
// const char* to [str] directly, but ignore that 


Here, String("Hello!") is a temporary object that only exists until the semicolon and therefore also an rvalue. str, however, is an lvalue. This means that this will call String(String&& s) as the constructor rather than String(const String& s).

While the class can function without String(String&& s), it can't function without String(const String& s), the copy constructor (ignoring the fact that the compiler will automatically generate one in this case). Now, if str was initialized with the fourth constructor rather than the third, if I understand it correctly, String("Hello!") will be created as a temporary value, and then its contents will be copied rather than moved to [str], which seems unneccessary in such cases.

I have two questions:
A) Have I understood these concepts correctly? If not, what am I misunderstanding?
B) Is there any other kind of constructor I should create? When is it appropriate to use lvalue references versus rvalue references?

I would greatly appreciate any help you could give.
Last edited on
A) Have I understood these concepts correctly? If not, what am I misunderstanding?


Well the problem here is that:
String str = String("Hello!");
will only result in 1 construction (of 'str') and not 2 separate constructions. That is, there's no temporary being created here. So the entire example is flawed.

Consider another example:

1
2
String foo("Hello!");
String bar(foo);


This will construct 2 objects:
- 'foo' will be constructed with const char* ctor
- 'bar' will be constructed with copy ctor.

notice that the move ctor is never used because you don't have any rvalue references here. If you want to do that, you can use std::move:

1
2
String foo("Hello!");
String bar( std::move(foo) );


Which will:
- construct 'foo' with const char* ctor
- construct 'bar' with the move ctor

B) Is there any other kind of constructor I should create? When is it appropriate to use lvalue references versus rvalue references?


The old "rule of 3" simply became the "rule of 5" with move semantics introduced.

Basically, in general you do not need to write copy ctors, move ctors, copy assignment operators, move assignment operators, or destructors unless your class is doing some kind of resource management. However, if you need to write any one of those... you will probably need to write all five of them.


Apart from that, any other constructors you write for your class can be whatever you want.
OK, let me try to give a better example:

1
2
3
4
5
int main()
{
	String s;
	s = String("Hello!");
}


This calls operator=(String&& s) instead of operator=(const String& s). Is this faster? (In this case, I doubt it makes a noticeable difference, but...)
This calls operator=(String&& s) instead of operator=(const String& s).


Correct.

Is this faster? (In this case, I doubt it makes a noticeable difference, but...)


It can be. String data is more easily moved than copied because a move does not require any additional data to be allocated.

Usually a move just involves a pointer swap -- the new object will take ownership of the old object's data. Whereas with a copy, the new object will have to create it's own version of the old object's data.
Yeah, that's what I figured was the point of it - just setting the lvalue's pointer to the rvalue's and then setting the rvalue's pointer to 0.

So, to summarize, I should create a copy constructor, a move constructor, a copy assignment operator and a move assignment operator. Now, say that I create a custom array class with a PushBack function - should I have both PushBack(const T& value) and PushBack(T&& value)?
So, to summarize, I should create a copy constructor, a move constructor, a copy assignment operator and a move assignment operator.


Only if you are doing resource management, which you probably shouldn't be.

If you use smart pointers and containers for everything like you should, then this is a non-issue and the compiler-provided constructors will work correctly and exactly how you'd want them to. You don't have to go through the extra work of writing these.


But yeah if this is for some academic exercise where you want to re-create vector for fun or something, then yeah you'd want to create all of those plus a destructor.

Now, say that I create a custom array class with a PushBack function - should I have both PushBack(const T& value) and PushBack(T&& value)?


No, you should do PushBack(T value);, then do a std::move() to move 'value' into your container.

- For L-value references, this will copy, but it will only be 1 copy which you'd have to do anyway to copy the value into your container
- For R-value references, this will move, which will move again into your container so there will be no copy.
- And you won't have to duplicate your code or have multiple functions that do the same thing.
Last edited on
Does std::move work on plain pointers (as in T* ptr)? I had assumed it only worked on existing containers such as std::vector.
Does std::move work on plain pointers (as in T* ptr)?


It works on anything. Though for basic types like pointers, ints, etc... a move would be the same thing as a copy.
Would that not mean that using PushBack(T value) would be slower than using its copy and move variants?
Not really. It will only perform (at worst) 1 copy, which is necessary for copying an object into a container. I suppose it involves an extra move, but that is trivial and the benefits of removing redundant code greatly outweigh that, IMO.

And keep in mind, with optimizations it's likely that the extra move will be eliminated entirely anyway, making it functionally identical to a move/copy pair of functions.
Last edited on
1
2
3
Array<String> arr;
String s = "Hello!";
arr.PushBack(s);


When using PushBack(T&&) and PushBack(const T&), this calls String's normal ctor once and its copy ctor once. When using PushBack(T), it calls String's constructor once and its copy ctor twice.
Keep in mind I'm suggesting your PushBack function look like this:

1
2
3
4
void Array::PushBack(T value)
{
    m_yourarraydata[x] = std::move(value);
}



That said... here is the worst case scenario for passing by value:

arr.PushBack(s);
- construct 's' however (unimportant)
- copy 's' to value
- move value to container
(note: only 1 copy, which is unavoidable since this is essentially a copy operation)

arr.PushBack( String("balls") );
- construct temporary however (unimportant)
- move temporary to value
- move value to container
(note: no copy)


So the only thing you're adding here is a move... which again... will likely be optimized out. There are no extra copies.
All right, so when adding a value to a container, I should just use T as a parameter?

If I had an array of initialized pointers, would the following code be "optimal"?:
1
2
void PushBack(T value)
{ ptr[x] = new T(value); }
All right, so when adding a value to a container, I should just use T as a parameter?


I would. Or at least I would if something like std::vector wasn't available ;P

If I had an array of initialized pointers, would the following code be "optimal"?:


For that, something like vector's 'emplace_back' would be more optimal -- where you actually call the desired constructor, rather than copy an existing object.
I don't quite understand what you mean - could you show a possible implementation of emplace_back on a C-style array?
Last edited on
Could I do something like this?
1
2
3
4
5
6
7
8
9
10
11
12
// This...
void PushBack(T value)
{
   ptr[x] = new T;
   *ptr[x] = std::move(value);
}

// ... or this?
void PushBack(T value)
{
   ptr[x] = new T(std::move(value));
}

Or will this copy the object rather than move it?
I don't quite understand what you mean - could you show a possible implementation of emplace_back on a C-style array?


I'm going to assume you mean a C++ style array class, and not a C-style array.

something like emplace_back would usually be done with variadic templates:

1
2
3
4
5
template<typename... Args>
void Array::emplace_back(Args... args)
{
    ptr[x] = new T(args...);
}

No, I mean an implementation on a C-style array, like int arr[] = {1, 2}.

Also, what about the question directly above?
Could I do something like this?
1
2
3
4
5
6
7
8
9
10
11
12
// This...
void PushBack(T value)
{
   ptr[x] = new T;
   *ptr[x] = std::move(value);
}

// ... or this?
void PushBack(T value)
{
   ptr[x] = new T(std::move(value));
}
Or will this copy the object rather than move it?
Last edited on
No, I mean an implementation on a C-style array, like int arr[] = {1, 2}.


Up until now we've been talking about an Array class =X

But you can't emplace_back on a C-style array because C-style arrays have a fixed size. But maybe I'm misunderstanding?

Or will this copy the object rather than move it?


You're using std::move, so it will move it. There is no copy in either of those examples.

Though the second example is superior because you move construct, which is better than default constructing only to move assign immediately after.
Last edited on
Disch wrote:
You're using std::move, so it will move it. There is no copy in either of those examples.

It's worth noting that std::move supplies you with an rvalue reference to the argument fed to it. It does not mean that object will be moved. There could very well be copies in either of those examples, depending on the type of T.

An emplace_back avoids copies by forwarding the arguments given to it directly to an object being constructed via placement new.
Pages: 12